🌞Moon Will Know
🥍

从 async 和 await 理解 generator

generator 函数

generator 函数是一种特殊的函数 function* 。在 generator 函数被调用时,它内部的代码不会被运行,而是返回一个 generator 对象。

generator

generator 拥有三个方法:
  • next() :被调用时它会恢复 generator 函数内部的运行,直到遇到 yield 语句。向 next() 方法传递参数,会作为上一个 yield 语句的返回值 。 next() 返回一个对象:
    • vlaueyield 语句后的值。
    • done:如果 generator 函数已经执行完毕,则为 true,否则为 false
  • throw():将一个错误注入到 generator 函数内,如果错误没有被处理,那么 generator 函数就会被关闭。
  • return():立即关闭 generator 函数,并将接受的值作为最终的返回值。

yield 是一条双向路

yield 可以为 next() 方法提供返回值,next() 方法也可以为上一个 yeild 语句提供返回值(绕死了)。
funtion* gen(x) { console.log(x); let r1 = yield x; console.log(r1); let r2 = yield r1; console.log(r2) } const generator = gen(1); generator.next(); // console.log(1) generator.next(2); // {value: 1, done: false} // cosnole.log(2) generator.next(4);// {value: 4, done: false} // cosnole.log(4) generator.next(); // {value:undefined,done:false}

generator 是可以迭代的

generator 具有 next() 方法,而且 next() 方法的返回体满足迭代器协议。所以 generator 是可以被迭代的。

老朋友其实是块糖

async

在函数前加上 async 关键字,会使函数的返回体变为一个 promise 对象。如果不存在 await 的话 async函数内部和普通函数表现无异。

await

await 关键字只能在 async 函数内部使用,如果 await 后面是一个 promise 对象。则会等待其执行完毕并返回 promise 的结果,如果 promise 内部抛出异常,那么则会直接被抛出。

拆开糖衣

看上去好像 asyncawaitgenerator 八杆子打不着一处,但其实 async 基于 generatorPromiseawait 则是 yield 的变体。 我们设想这样一个场景,我们有这样一个 generator 函数,函数内部有几个连续的 yield 语句,并且下一个语句依赖于上一个 yield 的结果。我们需要连续执行这个 generator 直到函数执行完毕。这样的场景是否类似于我们对 asyncawait 的使用场景。
const promise = function (val) { return new Promise((resolve) \\=> { setTimeout(() => { resolve(val + 1); }, 1000); }); }; function* gen() { let val1 = yield promise(1); console.log(val1); let val2 = yield promise(val1); console.log(val2); let val3 = yield promise(val2); console.log(val3); return val3; } const generator = gen(); const run = function (generator) { let result = generator.next(); function runDeep(generator, result) { if (!result.done) { result.value.then((res) => { result = generator.next(res); runDeep(generator, result); }); } else { return result.value; } } return runDeep(generator, result); }; run(generator);
所以当我们使用 async 函数时会将其包装为一个 generator 函数,并将 await 改写为 yield。这个 generator 函数返回的 generator 将会被一直调用,直到状态变为 done。 当然这其中并不是只有这么简单的逻辑,还包含错误处理等问题,和处理返回值类型等问题,接下来我们考虑自己实现下 async 方法。

实现 async

一个初步的实现,未经过测试,等我研究下 Promise/A+ 标准。
const promise = function (val) { return new Promise((resolve) => { setTimeout(() => { resolve(val + 1); }, 1000); }); }; function isPromise(val) { return Object.prototype.toString.call(val) === "[object Promise]"; } function iterator(generator, resolve, reject, val, err) { let result; try { result = err ? generator.throw(err) : generator.next(val); } catch (error) { return reject(error); } const { done, value } = result; if (done) { return resolve(value); } if (!isPromise(value)) { value = Promise.resolve(value); } value .then((res) => { iterator(generator, reject, res); }) .catch((error) => { return reject(error); }); } function _async(gen) { const generator = gen(); return new Promise((resolve, reject) => { iterator(generator, resolve, reject); }); } _async(function* gen() { let val1 = yield promise(1); console.log(val1); let val2 = yield promise(val1); console.log(val2); let val3 = yield promise(val2); console.log(val3); return val3; });
 

拓展:异步迭代器和异步生成器

当我需要异步地生成一组值时,我们首先会想起 Promise.all() 得到完整的结果对象后再进行完整操作。
但还有另一种方法可以实现异步迭代:
  1. 使用 [Symbol.asyncIterator] 代替 [Symbol.iterator]
  1. 使用 for await…of 代替 for…of
  1. next() 方法应该返回一个 Promise<{value:any,done:boolean}>
const value = [0, 1, 2]; value[Symbol.asyncIterator] = function () { return { current: 0, async next() { const result = await new Promise((resolve) => { setTimeout(() => { resolve(value[this.current]); }, 1000); }); this.current++; if (this.current > value.length) { return { done: true, value: undefined }; } else { return { done: false, value: result }; } }, }; }; (async () => { for await (let v of value) { console.log(v); } console.log('inner 1'); setTimeout(() => { console.log('inner 2'); }, 1000); })(); console.log('out 1'); setTimeout(() => { console.log('out 2'); }, 1000); // out 1 // 0 // out 2 // 1 // 2 // inner 1 // inner 2
在上面的代码中,将按间隔一秒的顺序打印数组中的成员。和 Promise.all() 的同时等待异步结果不同,asyncIterator 具有明显的顺序,或者说 Promise 嵌套。每一次循环想到于调用一次 next(),并得到 返回体中的 vlaue 的值。然后再次调用 next()。
for await…of 后面的部分将会在迭代完成后执行。

异步 generator

我们知道 generator 函数的一个作用就是用作迭代器。那么面对异步迭代的需求时,我们可以使用异步 generator 函数。
const gen = async function* () {}
在普通的 generator 函数前加上 async 关键字,然后在函数内愉快地使用 await 。

拓展2: yield*

我们知道 yield 可以暂停并返回一个值,但是当我们有一组值,或者另一个可迭代对象需要输出时,可以使用 yield* 来增强 yield 的能力。
const gen = function* () { yield* [0,1,2,3] } const generator = gen(); generator.next(); // {value:0,done:false} generator.next(); // {value:1,done:false} generator.next(); // {value:2,done:false} generator.next(); // {value:3,done:false} generator.next(); // {value:undefined,done:true}