绝大多数 Node.JS 程序员都会使用 async 和 await 关键字, 但是极少有人能真正弄明白 async 和 await 的原理. 这篇文章将从零 "构建" 出 async 和 await 关键字, 从而帮助理清 async 和 await 的本质.
先用一句话概括: async 和 await 是内置了执行器的 generator 函数.
什么是 generator 函数? 顾名思义, generator 函数就是一个生成器. 生成的是一个可以多次通过 .next() 迭代的对象, 例如, 定义一个 generator 函数如下:
- let g = function* () {
- yield 1
- yield 2
- return 3
- }
其中, yield 关键字定义每次迭代的返回值, 最后一个返回值用 return.
然后, 就可以用它来生成一个可迭代的对象:
- let iter = g()
- console.log(iter.next())
- console.log(iter.next())
- console.log(iter.next())
- console.log(iter.next())
以上代码执行的结果是:
- {
- value: 1, done: false
- }
- {
- value: 2, done: false
- }
- {
- value: 3, done: true
- }
- {
- value: undefined, done: true
- }
generator 函数也可以接收参数:
- let g = function* (a, b) {
- yield a
- yield b
- return a + b
- }
- let iter = g(1, 2)
- console.log(iter.next())
- console.log(iter.next())
- console.log(iter.next())
- console.log(iter.next())
执行结果:
- {
- value: 1, done: false
- }
- {
- value: 2, done: false
- }
- {
- value: 3, done: true
- }
- {
- value: undefined, done: true
- }
接下来是一个关键点: yield 关键字的值和 .next() 的参数的关系:
- let g = function* () {
- let ret = yield 1
- return ret
- }
- let iter = g()
- console.log(iter.next())
- console.log(iter.next(2))
以上代码的执行结果是:
- {
- value: 1, done: false
- }
- {
- value: 2, done: true
- }
可以看出, 第二次调用 .next() 的时候, 传入了参数 2, 这个 2 被赋值给了 ret. 也就是说,
let ret = yield 1
这行代码其实是被拆成两段执行的. 第一次调用 .next() 的时候, g 里面的代码开始执行, 执行到了 yield 1 这里, 就暂停并返回了. 这时打印 .next() 的返回值是 { value: 1, done: false }. 然后, 执行 .next(2) 的时候, 又回到了 g 里面的代码, 从 let ret = 2 开始执行.
理清楚这一执行过程非常重要. 因为, 这意味着:
如果我在 g 里面 yield 一个 Promise 出去, 在外面等 Promise 执行完之后, 再通过 .next() 的参数把结果传进来, 会怎样呢?
- let asyncSum = function(a, b) {
- return new Promise(resolve => {
- setTimeout(() => {
- resolve(a + b)
- }, 1000)
- })
- }
- let g = function* () {
- let ret = yield asyncSum(1, 2)
- console.log(ret)
- return ret
- }
- let iter = g()
- let p = iter.next().value
- p.then(sum => {
- iter.next(sum)
- })
执行结果就是等待一秒之后打印出 3:
- // 这里挂起了一秒钟
- 3
请细细品味上面代码里面的 g 函数:
- let g = function* () {
- let ret = yield asyncSum(1, 2)
- console.log(ret)
- return ret
- }
将其与下面代码进行对比:
- let g = async function () {
- let ret = await asyncSum(1, 2)
- console.log(ret)
- return ret
- }
发现了吧? 事实上 async 函数就是 generator 函数.
读者会问了, 不对啊, 我们调用 async 函数, 都是直接调用, 返回一个 Promise , 而不用像上面调用 g 那么麻烦的.
没错. 上面调用 g 的代码:
- let iter = g()
- let p = iter.next().value
- p.then(sum => {
- iter.next(sum)
- })
叫做 g 的执行器. 我们可以把它封装起来:
- let executor = function() {
- return new Promise(resolve => {
- let iter = g()
- let p = iter.next().value
- p.then(sum => {
- let ret = iter.next(sum)
- resolve(ret.value)
- })
- })
- }
- executor().then(ret => {
- console.log(ret)
- })
执行结果:
- // 挂起一秒钟
- 3 // g 里面的 console.log(ret)
- 3 // .then 里面的 console.log(ret)
实际上, node 的执行引擎悄悄地帮我们做了上面的事情, 当我们直接调用一个 async 函数时, 其实是在调用它的执行器.
原理讲到这里就完了. 下面是扩展部分.
上面的 executor 函数是仅仅针对这个例子里面的 g 写的. 那我们是否可能写一个通用的执行器函数, 适用于任何 generator 函数呢? 不管 generator 函数里面有多少个 yield , 这个执行器是否都可以自动全部处理完?
答案当然是肯定的, 用到了递归, 请看完整代码:
- let asyncSum = function(a, b) {
- return new Promise(resolve => {
- setTimeout(() => {
- resolve(a + b)
- }, 1000)
- })
- }
- let asyncMul = function(a, b) {
- return new Promise(resolve => {
- setTimeout(() => {
- resolve(a * b)
- }, 1000)
- })
- }
- let g = function* (a, b) {
- let sum = yield asyncSum(1, 2)
- let ret = yield asyncMul(sum, 2)
- return ret
- }
- function executor(generator, ...args) {
- let iter = generator.apply(this, args)
- let n = iter.next()
- if (n.done) {
- return new Promise(resolve => resolve(n.value))
- } else {
- return new Promise(resolve => {
- n.value.then(ret => {
- _r(iter, ret, resolve)
- });
- });
- }
- }
- function _r(iter, ret, resolve) {
- let n = iter.next(ret)
- if (n.done) {
- resolve(n.value)
- } else {
- n.value.then(ret => {
- _r(iter, ret, resolve)
- })
- }
- }
- executor(g, 1, 2).then(ret => {
- console.log(ret)
- })
执行结果:
- // 这里挂起了两秒钟
- 6
不过上面这个 executor 是个不完善的版本, 因为没有考虑错误的情况. 其实早在 async 和 await 还没有出现的 2013 年, 著名程序员 TJ Holowaychuk 就写了一个完善的 generator 执行器. 项目地址: https://github.com/tj/co . 其名字叫 co. 典型用法就是:
- co(function* () {
- var result = yield Promise.resolve(true);
- return result;
- }).then(function (value) {
- console.log(value);
- }, function (err) {
- console.error(err.stack);
- });
关于 async 和 await 的本质, 到这里就结束啦. 文章最后请细心的读者思考一个问题: 为什么 TJ Holowaychuk 的这个模块名字要叫做 co?
来源: https://www.cnblogs.com/blowing00/p/12469552.html