Promise - (九)重识async&await
第一代异步编程方案是:基于回调函数的异步编程,缺点是当实现异步串行时,会产生回调地狱
第二代异步编程方案是:基于Promise的异步编程,它使用then链式调用解决了异步任务串行时产生回调地狱的问题
那么Promise异步编程真的完美吗?
const fs = require('fs')
function read(path) {
return new Promise((resolve, reject) => {
fs.readFile(path, 'utf-8', function(err, data) {
if (err) return reject(err)
resolve(data)
})
})
}
read('a.txt').then(data=>{
return read(data)
}).then(data=>{
return read(data)
}).then(data => {
console.log(data)
})
缺点1:
then链式并不是最完美的异步串行书写方式,最完美的异步串行书写是 “异步任务之间无显示耦合关系, 异步任务间可以像同步代码一样自上而下执行”
这里then链式产生了显示耦合,且是链式执行
缺点2:
异步任务结果只能在then指定的回调函数中使用,或通过return传递给下一级then,但是无法越过下一级给下下一级使用
promise.then((data1)=>{
return data1
}).then((data1)=>{
// use data1
return data2
}).then((data2)=>{
// 无法获取到data1
})
那么这种场景多不多呢?很多,比如:
promise封装了一个接冷水的异步任务
promise.then((水)=>{
// 烧水
return 热水
}).then((热水)=>{
// 煮饭
return 饭
}).then((饭)=>{
// 吃饭
// 这里我还想喝热水,发现获取不到热水
})
而针对这两个缺点,ES6又推出了Generator和Iterator以及co,他们利用Generator的执行特点实现了异步任务串行的同步化书写,利用next传参实现了异步结果,同步接收
但是由于Generator函数,yield语义化太差,以及co执行器的模板化书写太冗余,最终推出他们的语法糖async和await
async相当于function*,即生成器函数
await相当于yield,即生成器函数中的yield
以及async函数执行时,被JS隐藏的co执行器,即async = 生成器 + 执行器 + promise封装, await = yield
const fs = require('fs')
function read(path, encode) {
return new Promise((resolve, reject) => {
fs.readFile(path, encode, function(err, data) {
if (err) return reject(err)
resolve(data)
})
})
}
async function fn() {
let val1 = await read('a.txt', 'utf-8')
let val2 = await read(val1, 'utf-8')
let val3 = await read(val2, 'utf-8')
console.log(val3)
}
fn()
// function* fn() {
// let val1 = yield read('a.txt', 'utf-8')
// let val2 = yield read(val1, 'utf-8')
// let val3 = yield read(val2, 'utf-8')
// console.log(val3)
// }
// function co(fn) {
// const iterator = fn()
// function next(value) {
// if (!value) value = iterator.next().value
// value.then(data => {
// let { value, done } = iterator.next(data)
// if (!done) {
// next(value)
// }
// })
// }
// next()
// }
// co(fn)
由于async,await是语法糖,所以他们底层还是基于Generator,Iterator,next,yield,co执行的,所以我们基于async和await的特性来用Generator,Iterator,next,yield,co重写一下
async函数会返回一个promise对象,promise对象的状态和结果由async函数执行的返回值决定
当return值为非promise对象时,则async函数返回一个成功态,且结果为return值的promise对象
当return值为promise对象时,则async函数返回一个新的promise对象,且状态和结果于return值的promise对象的状态和结果相同。
那么async函数的return值 对应到generator函数中是啥呢?
async函数中await对应 generator函数中的yield
async函数中return对应 generator函数中的return
而generator函数中yield和return有啥区别呢?
generator函数中yield产出的值,会被generator函数的返回值iterator迭代器对象通过调用next()获取到,且结果对象的done一定为false
generator函数中return也可以产出值,也会被generator函数的返回值iterator迭代器对象通过调用next()获取到,但是对应结果对象的done为true。
另外:
generator函数中yield可以获取下一次next传递过来的值
generator函数中return,没有下一次next,所以也就没有值给它
其次需要注意:generator函数中第一次产出值(yield或return)之前的语句是同步执行还是异步执行?
我们知道当generator函数返回的iterator调用next时,genrator函数体开始执行,执行过程中遇到yield就会暂停执行,遇到return就会结束执行,所以在第一次next后,第一次yield前的代码是同步执行的。
function* fn() {
yield 1
yield 3
yield 4
return 10
}
function co(fn) {
const iterator = fn()
let { value, done } = iterator.next() // 第一次next触发generator函数执行,执行遇到第一个产出值(yield或return)则暂停执行,将产出值返回
if (done) { // 若第一次next执行后,得到结果对象的done为true,表示迭代结束,则一定遇到了return或隐式return
return Promise.resolve(value) // 此时value是return的产出值,可能是隐式return undefined值
}
function next(value) {
// 第一次之后的next,需要等待上一次next得到的结果对象的value的最终结果
// 因为每一次next得到的结果对象中的value都可能是一个封装了异步任务的promise对象
// 如果value不是promise对象,则也要封装为一个promsie对象,实现await等待
value = value instanceof Promise ? value : Promise.resolve(value)
return value.then((data) => {
let { value, done } = iterator.next(data)
if (!done) {
return next(value)
} else {
return value
}
})
}
// 若第一次next执行后,得到的结果对象done为false,则继续next
return next(value)
}
const res = co(fn)
console.log(res)
await右侧的表达式一般为promise对象,但也可以是其他的值
如果表达式是promise对象,await返回的是promise成功的值,如果await的promise失败了,就会抛出异常,需要通过try...catch捕获异常处理;
如果表达式是其他值,直接将此值作为await的返回值;
async函数的await对应 generator函数的yield,我们需要按照await返回值的规则,来设计yield的返回值
function* fn() {
let v1 = yield 1
console.log(v1)
let v2 = yield Promise.resolve(2)
console.log(v2)
let v3 = yield new Promise((resolve, reject) => {
setTimeout(() => {
resolve('OK')
}, 1000)
})
console.log(v3)
}
function co(fn) {
const iterator = fn()
let { value, done } = iterator.next()
if (done) {
return Promise.resolve(value)
}
function next(value) {
value = value instanceof Promise ? value : Promise.resolve(value)
return value.then((data) => {
let { value, done } = iterator.next(data)
if (!done) {
return next(value)
} else {
return value
}
})
}
return next(value)
}
co(fn).catch(e=>{
console.log(e)
})
这里try...catch捕获await失败promise,我是没想到如何在yield上实现,看到tj大神的co库,发现也是co(fn*).catch(),这样实现的
await关键字可以暂停async函数向下执行,知道await的promise返回结果
这个功能的实现,其实就是
yield可以暂停function*函数向下执行,直到下一次next调用,但是下次next的调用依赖于上一次next的获得的结果对象的value(promise对象)返回结果
相关文章
- 设计模式-责任链模式&策略模式
- Java中&和&&,|和||的区别(超详细讲解),细节请必会!
- ECCV 2022 | 清华&Meta提出HorNet,用递归门控卷积进行高阶空间相互作用
- 图像校正优化软件:Perfectly Clear QuickDesk & QuickServer中文版 Mac下载
- 00SEC-D&D数据泄露报警日报【第5期】
- 零零信安-D&D数据泄露报警日报【第21期】
- 《快学BigData》--Linux常用解压软件命令&压缩软件命令(6)
- ECCV2022 &CVPR2022论文速递2022.7.27!
- Deep dive#Milvus 新版本功能解读 & 测试分享
- 7 Papers & Radios | 21℃室温超导引爆物理圈;微软发布视觉ChatGPT
- 7 Papers & Radios | Meta「分割一切」AI模型;从T5到GPT-4盘点大语言模型
- AMP MySQL升级提升数据库性能的必要之举(amp mysql升级)
- IIS&Apache攻击记录分析篇
- PHPnl2br函数将换行字符转成<br>
- 修复IE9&safari的sort方法
- android之HttpPost&HttpGet使用方法介绍