以前我在關於Promise的文章中提到了co
這個庫。在這篇文章裏,我將寫一寫本身對它的認識。git
Trust me,用了co
庫,你不想用別的,來它半斤異步調用你一口能吃仨。github
可是我對Tj大神的co庫源碼談不上深刻理解。因此,若有亂講,歡迎指正。segmentfault
我這裏默認讀者對Promise
和Generator
有必定的認識。數組
先安利本身寫的兩篇關於Promise的文章:瀏覽器
下面我就來談談co
這個牛逼的庫。async
幹嗎,咱們不是講ES6麼,怎麼跳到ES7了?函數
由於co
要作的事情,就是ES7的async/await
要作的事情。
也就是說,這種解決異步的思路,已經在ECMA標準的考慮之中了。未來咱們瀏覽器的JS引擎就能夠原生實現這件事而不是經過JavaScript代碼模擬。要知道,引擎的實現和代碼的實現那是徹底兩碼事。ui
多一句嘴:有些同窗混淆了ECMA標準、引擎支持和代碼實現的聯繫。
這裏引用老趙在知乎裏面回答問題時說的一句話:
ES7是個標準,定義的是what to do不是how to do,爲何好多人仍是搞不清這二者的區別。
ECMAScript定義了一些JavaScript語言層面要作的事情,這是一個標準。之因此要制定這個標準,是爲了防止瀏覽器各自爲政而出現JS引擎對同一行代碼的解釋出現不一樣的狀況。
也就是說,ECMA制定標準,咱們就能夠按照這個標準來寫JavaScript代碼。寫好的JavaScript代碼由瀏覽器的JS引擎來解釋,最終變成計算機能讀懂的代碼來執行。
上代碼:
var foo = function(){ return new Promise(resolve => { // 異步操做以後 resolve('OK'); }); } async funtion bar(){ var result = await foo(); console.log(result); } bar(); // ==> 打印'OK'
咱們注意到,這段代碼用了兩個新的關鍵字async
和await
。並且有兩件神奇的事情發生了:
bar
函數中包含了一個返回Promise
對象的語句,並且Promise
中存在異步代碼。可是這條語句接下來的語句明顯是等待Promise
對象中的代碼異步執行完畢以後才執行的。(不然不會獲得異步以後的值)
Promise
對象resolve
的值,並無在then
中進行處理,而是直接做爲返回值返回到Promise
對象外面了.
這就是async/await
的魔法。在函數前面加上async
關鍵字以後,內部的代碼會識別await
關鍵字。此時假設await
後面的語句返回一個Promise
對象,那麼執行的代碼將會等待,直到Promise
對象變爲resolve
狀態。而且Promise
對象中resolve
的值將直接做爲await
語句的返回值返回。而後再執行await
語句以後的語句。
今後咱們就能夠無痛的擼異步代碼,媽媽不再用擔憂回調金字塔的出現和異步流程邏輯搞不定的狀況了!
另外一個奇妙的事情就是,率先支持這一特性的瀏覽器竟然是微軟的Edge。大概是由於C#
語言早就出現async/await
,而且TypeScript
也支持這一特性的緣故吧。
咱們但願全部的瀏覽器都及早支持這一特性。可是值得欣喜的一點就是,雖然V8尚未支持,Tj大神早就利用Generator
的方式實現了一個ES6版本的async/await
!(膜拜臉)
一樣是上面的邏輯,咱們用co
實現一次:
// 首先咱們須要將co引入,假設咱們使用commonJS的方式 const co = require('co'); var foo = function(){ return new Promise(resolve => { // 異步操做以後 resolve('OK'); }); } co(function* (){ var result = yield foo(); console.log(result); }); // ==> 打印'OK'
咱們看到,co
函數接收一個Generator
生成器函數做爲參數。執行co
函數的時候,生成器函數內部的邏輯像async
函數調用時同樣被執行。不一樣之處只是這裏的await
變成了yield
。
要實現以上的邏輯,結合Generator
的特性,co
函數應該:
在函數體內將Generator生成器函數執行並生成生成器實例(在此命名爲gen
),而後經過gen.next
方法的調用,不斷執行生成器函數內部的代碼。
執行next
方法以後,返回的Promise
在生成器函數執行環境以外執行,並取出resolve
值,做爲返回值做爲next
方法的參數返回到Generator
執行環境中。
基於以上兩點,咱們能夠大致實現一個簡化版的co
,代碼以下:
const co = function(genFunc){ const gen = genFunc(); // 獲得生成器實例 const deal = (val) => { const res = gen.next(val); // 這裏處理了異步邏輯, // 在回調中去遞歸,不斷執行next // 這樣就將resolve的值傳回了Generator res.value.then(result => deal(result)); } deal(); // 第一次觸發遞歸 }
去掉括號等等,只有短短六行代碼。
原理性的東西大約就是這樣了。可是co
作的不止這些。
以前co
的yield
後的語句並不支持Promise
對象,而是一個特殊的函數,叫作thunk
。目前co
兩者都支持。
此處我並不打算重複性解釋thunk
版本,由於原理性的東西實現起來是差很少的。
co
函數是有返回值的,也是一個Promise
對象。
當生成器函數內的邏輯執行完畢且沒有錯誤以後,這個Promise
對象(co
返回值)變爲resolve
狀態,且將生成器的返回值做爲resolve
出來的值。
若生成器函數內返回一個Promise
對象,那麼co
函數返回值就是這個Promise
對象。
若生成器函數拋出了錯誤,那麼這個錯誤做爲reject
出來的值,將Promise
對象的狀態變爲reject
。
這樣咱們就能夠將錯誤放進其返回值的.catch
方法中統一處理。
在生成器函數內部,咱們也可使用try...catch
語句獲取錯誤對象。
生成器的yield
後面能夠跟一個元素值爲Promise
對象的數組,這個數組內Promise
對象內的異步邏輯將併發執行,並返回一個數組。(相似於Promise.all
方法)
假設生成器執行以前須要從外部傳入參數,co
庫提供了一個方法:
var fn = co.wrap(function* (val) { return yield Promise.resolve(val); }); fn(true).then(function (val) { });
以上是一點微小的看法。謝謝指正。