generator 函数
generator
函数是一种特殊的函数 function*
。在 generator
函数被调用时,它内部的代码不会被运行,而是返回一个 generator
对象。generator
generator
拥有三个方法:next()
:被调用时它会恢复generator
函数内部的运行,直到遇到yield
语句。向next()
方法传递参数,会作为上一个yield
语句的返回值 。next()
返回一个对象:vlaue
:yield
语句后的值。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
内部抛出异常,那么则会直接被抛出。拆开糖衣
看上去好像
async
和 await
与 generator
八杆子打不着一处,但其实 async
基于 generator
和 Promise
,await
则是 yield
的变体。
我们设想这样一个场景,我们有这样一个 generator
函数,函数内部有几个连续的 yield
语句,并且下一个语句依赖于上一个 yield
的结果。我们需要连续执行这个 generator
直到函数执行完毕。这样的场景是否类似于我们对 async
和 await
的使用场景。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()
得到完整的结果对象后再进行完整操作。但还有另一种方法可以实现异步迭代:
- 使用
[Symbol.asyncIterator]
代替[Symbol.iterator]
- 使用
for await…of
代替for…of
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}