從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函數。

相關文章
相關標籤/搜索