异步无处不在:Generator 异步(五)
(。・∀・)ノ゙嗨,我是你稳定更新、持续输出的勾勾。

关于异步的知识点,终于要结束了!
本篇就来重点说说如何将 Generator 写进异步中。
Generator 异步方案
将调用 ajax 的代码写到生成器函数的 yield 后面,每次的异步执行,都要在 yield 中暂停,调用的返回结果是一个 Promise 对象。
我们可以从迭代器对象的 value 属性获取到 Promise 对象,然后使用 .then 进行链式调用处理异步结果。
结果处理的代码叫做执行器,就是具体负责运行逻辑的代码。
function ajax(url) {……}// 声明一个生成器函数function * fun(){yield myAjax('./d1.json')yield myAjax('./d2.json')yield myAjax('./d3.json')}// 返回 遍历器对象var f = fun();// 生成器函数的执行器// 调用 next 方法,执行异步代码var g = f.next();g.value.then(data=>{console.log(data);// console.log(f.next());g = f.next();g.value.then(data=>{console.log(data)// g.......})})
而执行器的逻辑中,是相同嵌套的,因此可以写成递归的方式对执行器进行改造:
// 声明一个生成器函数function * fun(){yield myAjax('./d1.json')yield myAjax('./d2.json')yield myAjax('./d3.json')}// 返回 遍历器对象var f = fun();// 递归方式 封装// 生成器函数的执行器function handle(res){if(res.done) return;res.value.then(data=>{console.log(data)handle(f.next())})}handle(f.next());
然后,再将执行的逻辑,进行封装复用,形成独立的函数模块。
function co(fun) {// 返回 遍历器对象var f = fun();// 递归方式 封装// 生成器函数的执行器function handle(res) {if (res.done) return;res.value.then(data => {console.log(data)handle(f.next())})}handle(f.next());}co(fun);
封装完成后,我们再使用时只需要关注 Generator 中的 yield 部分就行了。
function co(fun) {……}function * fun(){yield myAjax('./d1.json')yield myAjax('./d2.json')yield myAjax('./d3.json')}
此时你会发现,使用 Generator 封装后,异步的调用就变得非常简单了。
但是,这个封装还是有点麻烦,有大神帮我们做了这个封装,相当强大:https://github.com/tj/co ,感兴趣可以研究一下。
而随着 JS 语言的发展,更多的人希望类似 co 模块的封装能够写进语言标准中,而我们直接使用这个语法规则就行了。
其实你也可以对比一下,使用 co 模块后的 Generator 和 async 这两段代码:
// async / awaitasync function callAjax(){var a = await myAjax('./d1.json')console.log(a);var b = await myAjax('./d2.json');console.log(b)var c = await myAjax('./d3.json');console.log(c)}// 使用 co 模块后的 Generatorfunction * fun(){yield myAjax('./d1.json')yield myAjax('./d2.json')yield myAjax('./d3.json')}
你应该也发现了,async 函数就是 Generator 语法糖,不需要自己再去实现 co 执行器函数或者安装 co 模块。
写法上将 * 星号去掉换成放在函数前面的 async,把函数体的 yield 去掉,换成 await。
简直完美!
async function callAjax(){var a = await myAjax('./d1.json')console.log(a);var b = await myAjax('./d2.json');console.log(b)var c = await myAjax('./d3.json');console.log(c)}callAjax();
我们再来看一下 Generator。
相信下面的代码,你能很轻松地阅读。
function * f1(){console.log(11)yield 2;console.log('333')yield 4;console.log('555')}var g = f1();g.next();console.log(666);g.next();console.log(777);
代码运行结果:

带着 Generator 的思路,我们再回头看看那个 async 的面试题。
请写出以下代码的运行结果:
setTimeout(function () {console.log('setTimeout')}, 0)async function async1() {console.log('async1 start')await async2();console.log('async1 end')}async function async2() {console.log('async2')}console.log('script start')async1();console.log('script end')
运行结果:

是不是恍然大明白呢(●'◡'●)。
推荐阅读:
前端人因为 Vue3 的 Ref-sugar 提案打起来了!
点点“赞”和“在看”,保护头发,减少bug。
