异步无处不在:Generator 异步方案(五)

232 阅读3分钟

关于异步的知识点,终于要结束了!

本篇就来重点说说如何将 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 封装后,异步的调用就变得非常简单了。

但是,这个封装还是有点麻烦,有大神帮我们做了这个封装,相当强大:github.com/tj/co ,感兴趣可以研究一下。

而随着 JS 语言的发展,更多的人希望类似 co 模块的封装能够写进语言标准中,而我们直接使用这个语法规则就行了。

其实你也可以对比一下,使用 co 模块后的 Generator 和 async 这两段代码:

//  async / 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)
 }
  
// 使用 co 模块后的 Generator
 function * 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')

运行结果:

是不是恍然大明白呢(●'◡'●)。