从Co剖析和解释generator的异步原理

generator的异步原理

一样的,这个内容感谢@MPJ老师在youtube上的funfunfuntion课程。 当年我看阮老师的generator云里雾里,实在搞不懂,为何yield出来就能够异步了?? @MPJ用了相反的过程,先给一个应用场景,再去实现异步,我算是真的搞清楚,generator异步的远离了,和你们分享~~javascript

博客和web项目更新传送门java

从实际应用场景开始

假设咱们有一个异步的请求,想要去经过api获取一些数据。这里借助node-fetch库来获取数据。 fetch能够异步的获取数据,并返回一个promise,因此常规的异步操做和写法,大体以下node

var fetch = require('node-fetch');
fetch('http://jasonplacerholder.typecoder.com/posts/1')
    .then( res => res.json() )
    .then( post => post.title )
    .then( x => console.log('Title: ',x))
复制代码

好了,以上的代码就是一个获取api,并拿到api中的title内容。 关于promise这里很少说,fetch返回的就是一个promise。git

genetor实现

那么若是使用generator会如何实现实现一样的一个异步操做呢? 这里先给结果,再来分析实现原理。这里记住co,这个co是干吗的,一会分析并实现一个咱们本身的co函数。github

co接收一个genetor,因此咱们能够认为co就是一个generator的发动机,或者自动执行器。web

const co = require('co');
co(function *() {
    const url = 'http://jasonplacerholder.typecoder.com/posts/1';
    const response = yield fetch(url);
    const post = yield response.json();
    const title = post.title;
    console.log('Title: ',title);
})

复制代码

好了,结束,执行后,会输出一样的结果,彷佛和promise没有两样。下面先简单的逐行分析,来看看在genetor中,作了什么。编程

//从genetor的第一行开始
第一行: 定义了url
第二行: 声明response,并将fetch(url)的结果.....yield
stop...
What is yield???
复制代码

嗯,因此,这个genetor的yield是干什么的?这是genetor和普通函数的不一样之处,也是它能够作异步的基础。不一样与普通函数,genetor遇到了yield以后,会将yield后面的处理内容抛出。json

genetor: 运行呀---运行呀---运行呀--yield? What?这是什么鬼,我搞不定,老大你帮我搞定后再加我---out..api

outer(执行器co): 收到yield返回的结果,处理----返回给genetorpromise

genetor: 收处处理结果---运行---yeild?这又是什么?你帮我搞定,out...

outer(执行器co): 收到yield返回的promise,处理---返回给genetor

这就是异步的原理了,genetor遇到yield会把任务丢出去,它就暂时不运行了。 咱们知道,yield丢出去的是一个iterator,当调用next()的时候,会返回genetor中。 因此其实co就是一个自动触发和调度next()的函数。

实现co

知道了原理,咱们本身来实现这个过程。而后就会比较清除整个过程了。

咱们把函数改一下

run(function *() {
    const url = 'http://jasonplacerholder.typecoder.com/posts/1';
    const response = yield fetch(url);
    const post = yield response.json();
    const title = post.title;
    console.log('Title: ',title);
})

function run(generator) {
    const iterator = generator(); //genetor执行会返回一个iterator,而后调用next()才会执行到下一个yield
    iterator.next(); //这里打印出来的结果看一下是{value: Promise {<pending>},done:false}

}
复制代码

解释: 就如上面genetor和outer的对话,遇到yield,genetor会说:"我不知道怎么搞这个promise,你来搞吧,给你..“ 因而,外面的就会接住这个promise

咱们继续写

function run(generator) {
    const iterator = generator(); //genetor执行会返回一个iterator,而后调用next()才会执行到下一个yield
    const iteration = iterator.next(); //这里打印出来的结果看一下是{value: Promise {<pending>},done:false}
    const promise = iteration.value;
    promise.then(x => iterator.next(x)) //ok,外部帮忙处理了promise,而后处理的结果,咱们须要返回genetor,使其继续运行
    //这个时候,genetor中的response拿到了值,就等于这里的x
}
复制代码

分析到这里,程序已经获得了response。 可是,下一句,立马又遇到了response.json(),一样又会丢出去一个内容,所以,咱们这里再处理一下,以下:

function run(generator) {
    const iterator = generator(); //genetor执行会返回一个iterator,而后调用next()才会执行到下一个yield
    const iteration = iterator.next(); //这里打印出来的结果看一下是{value: Promise {<pending>},done:false}
    const promise = iteration.value;
    promise.then(x => {
        const anotherIterator = iterator.next(x);//注意,iterator.next()的含义,一方面会将运算结果返回,另外一方面,genetor会继续将下一个yield的任务抛出,仍然是一个iterator
        const anotherPromise = anotherIterator.value;
        anotherPromise.then(post => iterator.next(post))
        //到此,由于iterator再也没有yield,因此不会再次返回iterator了,也不用调用next()
    }) 
}
复制代码

至此,模拟的co方法已经实现了。

流程以下:

  1. run传入一个genetor并运行,得到一个iterator(generator())
  2. 调用next()方法,获取到iteration,iteration的value是yield fetch(url)的结果,也即一个Promise。
  3. yield返回出的任务,由外部执行和处理,结束后在返回,因而使用then方法。
  4. 处理后的结果为x,调用iterator.next(x)把x返回的同时,拿到了下一个yield的抛出的任务。
  5. 处理任务,获得post,并经过next(post)返回给genetor。
  6. 嗯,我拿到大家处理的结果了,下一次我遇到yield还给大家,反正我不会,我也不会学,这任务都是大家的。

也就是说,genetor的异步,就在于能将线程弹出,遇到yield后,交出线程。因此,咱们作一个可以自动执行和触发genetor的执行器,就能够实现异步编程,并且看起来和同步的写法很类似。 这就是库co作的事情。

完善咱们本身的co

刚才只有两个yield,咱们但愿方法有通用性,咱们写个递归,让它能不断的触发

function run(genetor) {
    const iterator = genetor();
    function autoRun(iteration) {
        if(iteration.done) {return iteration.value;}
        const anotherPromise = iteration.value;
        anotherPromise.then(x => {
            return autoRun(iterator.next(x));
        })
    }
    return autoRun(iterator.next());
}

复制代码

好了,这样就完成了咱们本身的简易版co函数。

相关文章
相关标签/搜索