简单手写async函数,让生成器函数自动执行
async
函数实现了异步代码以近乎同步的方式写,可读性强,使用的时候真是非常方便。
其本质是生成器函数的语法糖,本文尝试手写一个函数来替换async
函数,去掉糖衣看看。
async
函数的 demo
先来一个async
函数的 demo:
const getData = () => new Promise((resolve) => setTimeout(() => resolve('data'), 1000)); async function test() { const data = await getData(); console.log('data: ', data); const data2 = await getData(); console.log('data2: ', data2); return 'success'; } // 这样的一个函数 应该再1秒后打印data 再过一秒打印data2 最后打印success test().then((res) => console.log(res));
换成生成器函数的 demo
每个async
函数,其实是在类似执行一个生成器
函数,而转化成生成器函数也很简单:
- 去掉
async
- 加上
*
- 将
await
换成yield
示例转化下:
const getData = () => new Promise((resolve) => setTimeout(() => resolve('data'), 1000)); function* testGen() { const data = yield getData(); console.log('data: ', data); const data2 = yield getData(); console.log('data2: ', data2); return 'success'; }
生成器函数的基础知识
运行完整个生成器函数的话,
next
的调用次数 =yield
的出现次数 + 1
先看个简单的 demo:
function* gen() { const data = yield '第 1 个yield'; console.log('data: ', data); const data2 = yield '第 2 个yield'; console.log('data2: ', data2); return 'success'; } const it = gen(); var { value, done } = it.next('第 1 个next'); var { value, done } = it.next('第 2 个next'); var { value, done } = it.next('第 3 个next');
理解next
和yield
语句尤为关键
next
让函数内部代码开始执行,next
本身是个函数,
- 其返回值是一个对象,第n个next函数的返回值(value属性) 是 第n个
yield
后面的内容 或 生成器函数的返回值,done
是标志函数是否执行完。 - 但是第n个
yield
,却是替换成 第n+1个next的参数
next
执行的时候,遇到yield
就暂停
将生成器函数以伪代码的方式,表达下:
伪代码里将yield
和next
全部换掉,并注上暂停的点,这样我觉得就理解的七七八八了
function* gen() { // const data = yield '第 1 个yield'; const data = '第 2 个next'; // 第 2 个 next的参数,这也是第一个暂停处,注意并没赋值 console.log('data: ', data); // const data2 = yield '第 2 个yield'; const data2 = '第 3 个next'; // 第 3 个 next的参数,这也是第二个暂停处,注意并没赋值 console.log('data2: ', data2); return '生成器函数的返回值'; // 生成器函数运行完毕 } // it是迭代器 const it = gen(); // 第一个next函数的返回值是 第一个yield后面的内容 // var { value, done } = it.next('第 1 个next'); var value = '第 1 个yield', done = false; // 第一个next函数的返回值是 第一个yield后面的内容 // var { value, done } = it.next('第 2 个next'); var value = '第 2 个yield', done = false; // 第二个next函数的返回值是 第二个yield后面的内容 // var { value, done } = it.next('第 3 个next'); var value = '生成器函数的返回值', done = true; // 注意,这里done为true,所以value就是是生成器函数的返回值
所以可见,遇到yield
赋值语句的时候,一定提醒自己,跟yield
后面的内容没有一毛钱关系!
遇到next
的赋值语句的时候,value
和next
的参数没有一毛钱关系!
让生成器手动执行
再回到主体的生成器函数的例子:
const getData = () => new Promise((resolve) => setTimeout(() => resolve('data'), 1000)); function* testGen() { const data = yield getData(); // data = 第2个next的参数 console.log('data: ', data); const data2 = yield getData(); // data = 第3个next的参数 console.log('data2: ', data2); return 'success'; }
想让data
是数据的话,跟yield
后面的并没关系,而是跟next
的传入有关。
const it = testGen(); // value是 第1个yield的后面的getData() 其实就是promise实例, let res = it.next(); let promise = res.value; let done = res.done; promise.then((data) => { // promise是 第2个yield的后面的getData() 其实就是promise实例。注意这里的next参数才是上面data的赋值 res = it.next(data); promise = res.value; done = res.done; promise.then((data) => { // done为true,promise是 生成器函数的返回值。注意这里的next参数才是上面data2的赋值 res = it.next(data); promise = res.value; done = res.done; }); });
让生成器函数自动执行
将上面的过程封装成一个函数,让其自动执行生成器函数,可以将重复的地方拿出来:
function co(gen, ...args) { return (...args) => { const it = gen(...args); // 首次先运行 let res = it.next(); let promise = res.value; let done = res.done; // 重复的部分封装下: const fn = () => { if (done) { return Promise.resolve(promise); } promise.then((data) => { // done为true,promise是 生成器函数的返回值。注意这里的next参数才是上面data2的赋值 res = it.next(data); promise = res.value; done = res.done; // 继续往前走 fn(); }); }; fn(); }; } co(testGen)();
更好的版本
考虑到异常情况,其实更复杂一点。
这边直接搬运手写 async await 的最简实现(20 行)
function asyncToGenerator(generatorFunc) { return function () { const gen = generatorFunc.apply(this, arguments); return new Promise((resolve, reject) => { function step(key, arg) { let generatorResult; try { generatorResult = gen[key](arg); } catch (error) { return reject(error); } const { value, done } = generatorResult; if (done) { return resolve(value); } else { return Promise.resolve(value).then( (val) => step('next', val), (err) => step('throw', err) ); } } step('next'); }); }; }
思路:
function asyncToGenerator(generatorFunc) { // 返回的是一个新的函数 return function() { // 先调用generator函数 生成迭代器 // 对应 var gen = testG() const gen = generatorFunc.apply(this, arguments) // 返回一个promise 因为外部是用.then的方式 或者await的方式去使用这个函数的返回值的 // var test = asyncToGenerator(testG) // test().then(res => console.log(res)) return new Promise((resolve, reject) => { // 内部定义一个step函数 用来一步一步的跨过yield的阻碍 // key有next和throw两种取值,分别对应了gen的next和throw方法 // arg参数则是用来把promise resolve出来的值交给下一个yield function step(key, arg) { let generatorResult // 这个方法需要包裹在try catch中 // 如果报错了 就把promise给reject掉 外部通过.catch可以获取到错误 try { generatorResult = gen[key](arg) } catch (error) { return reject(error) } // gen.next() 得到的结果是一个 { value, done } 的结构 const { value, done } = generatorResult if (done) { // 如果已经完成了 就直接resolve这个promise // 这个done是在最后一次调用next后才会为true // 以本文的例子来说 此时的结果是 { done: true, value: 'success' } // 这个value也就是generator函数最后的返回值 return resolve(value) } else { // 除了最后结束的时候外,每次调用gen.next() // 其实是返回 { value: Promise, done: false } 的结构, // 这里要注意的是Promise.resolve可以接受一个promise为参数 // 并且这个promise参数被resolve的时候,这个then才会被调用 return Promise.resolve( // 这个value对应的是yield后面的promise value ).then( // value这个promise被resove的时候,就会执行next // 并且只要done不是true的时候 就会递归的往下解开promise // 对应gen.next().value.then(value => { // gen.next(value).value.then(value2 => { // gen.next() // // // 此时done为true了 整个promise被resolve了 // // 最外部的test().then(res => console.log(res))的then就开始执行了 // }) // }) function onResolve(val) { step("next", val) }, // 如果promise被reject了 就再次进入step函数 // 不同的是,这次的try catch中调用的是gen.throw(err) // 那么自然就被catch到 然后把promise给reject掉啦 function onReject(err) { step("throw", err) }, ) } } step("next") }) } }