前言
没有读过阮一峰 async 函数这篇文章 的小伙伴强烈建议读一下。
async函数式什么
async
就是Generator
函数的语法糖。
如下有一个Generator
函数,一次读取两个文件。
const fs = require('fs')
const readFile = function (fileName) {
return new Promise(function (resolve, reject) {
fs.readFile(fileName, function(error, data) {
if (error) return reject(error)
resolve(data)
})
})
}
const gen = function* () {
const f1 = yield readFile('/etc/fstab')
const f2 = yield readFile('/etc/shells')
console.log(f1.toString())
console.log(f2.toString())
}
以上代码改成async
函数就是下面这样。
const asyncReadFile = async function() {
const f1 = await readFile('/etc/fstab')
const f2 = await readFile('/etc/shells')
console.log(f1.toString())
console.log(f2.toString())
}
比较之后,我们发现,async
函数就是将Generator
函数的*
星号替换成async
,将yield
替换成await
,仅此而已。
async
函数对Generator
函数的改进,体现在一下四点
。
(1) 内置执行器
Generator
函数的执行必须依靠执行器,所有才有了co
模块,而async
函数自带执行器。也就是说,async
函数的执行,与普通函数一摸一样,只要一行。
asyncReadFile()
上面的代码调用了asyncReadFile
函数,然后它就会自动执行,输出最后结果。这完全不像 Generator
函数,需要调用next
方法,或者用co
模块,才能真正执行,得到最后结果。
(2) 更好的语义
async
和await
比起星号
和yield
,语义更清楚了。async
表示函数里有异步操作,await
表示紧跟在后面的表达式需要等待结果。
(3) 更广的适用性
co
模块约定,yield
命令后面只能是 Thunk
函数或 Promise
对象,而async
函数的await
命令后面,可以是 Promise
对象和原始类型的值(数值、字符串和布尔值,但这时会自动转成立即 resolved
的Promise
对象)。
(4) 返回值是Promise
async
函数的返回值是Promise
对象,这比Generator
函数的返回是Interator
对象方便多了。你可以用then
方法指定下一步的操作。
进一步说,async
函数完全可以看作多个异步操作,包装成一个Promise
对象,而await
命令就是内部then
命令的语法糖。
async 函数的实现原理
强烈建议读 async 函数的实现原理
async
函数的实现原理,就是将Generator
函数和自动执行器,包装在一个函数里。
async function fn(args) {
// ...
}
// 等同于
function fn(args) {
return spawn(function * () {
// ...
})
}
所有的async
函数都可以写成上面的第二种形式,其中的spawn
函数就是自动执行器。
下面我们来实现一下spawn
函数,基本就是前文自动执行器的翻版。
// 原理非常简单,就是一个递归调用
function spawn(genF) { // 要返回一个promise
// genF为传入的generator函数
return new Promise(function(resolve, reject) {
// 先执行一下generator函数
const gen = genF()
function step(nextF) {
let next
try {
next = nextF() // 首次返回gen.next(undefined)
} catch(e) {
return reject(e)
}
// generator函数调用next.done返回true
if (next.done) {
return resolve(next.value)
}
// 执行resolve并将next.value传入
Promise.resolve(next.value).then(function(v) {
// 递归调用step函数,并将gen.next(v)的函数传入
step(function () {
return gen.next(v)}
}
}, function(e){
step(function() {return gen.throw(e)})
})
}
step(function () {
return gen.next(undefined)
})
})
}
原理总结
- 调用
fn()
函数返回调用spawn()
函数,参数是一个Generator
函数,spawn
函数是一个自动执行器 spawn
函数返回一个promise
- 在
promise
中先执行一下generator
函数 - 调用
step
函数,参数为函数,且返回gen.next(undefined)
,即首次调用,停到第一个yield
处 - 在
step
函数中,声明next
,判断generator
函数调用next.done
是否返回true
- 未返回
true
,则调用Promise.resolve
,将此次的value
传入step
,进行递归调用,直到next.done
为true
- 最终返回的结果为
promise
,可以继续进行then
调用