generator的非同步原理
同樣的,這個內容感謝@MPJ老師在youtube上的funfunfuntion課程。 當年我看阮老師的generator雲裡霧裡,實在搞不懂,為什麼yield出來就可以非同步了?? @MPJ用了相反的過程,先給一個應用場景,再去實現非同步,我算是真的搞清楚,generator非同步的遠離了,和大家分享~~
部落格和web專案更新傳送門
從實際應用場景開始
假設我們有一個非同步的請求,想要去通過api獲取一些資料。這裡藉助node-fetch
庫來獲取資料。
fetch
可以非同步的獲取資料,並返回一個promise,所以常規的非同步操作和寫法,大致如下
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。
genetor實現
那麼如果使用generator會如何實現實現同樣的一個非同步操作呢? 這裡先給結果,再來分析實現原理。這裡記住co
,這個co是幹嘛的,一會分析並實現一個我們自己的co函式。
co接收一個genetor,所以我們可以認為co就是一個generator的發動機,或者自動執行器。
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後面的處理內容丟擲。
genetor: 執行呀---執行呀---執行呀--yield? What?這是什麼鬼,我搞不定,老大你幫我搞定後再加我---out..
outer(執行器co): 收到yield返回的結果,處理----返回給genetor
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
方法已經實現了。
流程如下:
- run傳入一個genetor並執行,獲得一個iterator(generator())
- 呼叫next()方法,獲取到iteration,iteration的value是
yield fetch(url)
的結果,也即一個Promise。 - yield返回出的任務,由外部執行和處理,結束後在返回,於是使用then方法。
- 處理後的結果為
x
,呼叫iterator.next(x)
把x返回的同時,拿到了下一個yield
的丟擲的任務。 - 處理任務,得到
post
,並通過next(post)
返回給genetor。 - 嗯,我拿到你們處理的結果了,下一次我遇到
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函式。