# 神奇的co
* [co是什么?](https://i5ting.github.io/wechat-dev-with-nodejs/async/co.html#co是什么)
* [源碼解析](https://i5ting.github.io/wechat-dev-with-nodejs/async/co.html#源碼解析)
* [co引出的“血案”](https://i5ting.github.io/wechat-dev-with-nodejs/async/co.html#co引出的血案)
## co是什么?
[https://github.com/tj/co](https://github.com/tj/co)
> 官方說:The ultimate generator based flow-control goodness for nodejs (supports thunks, promises, etc)
我的解釋
* 是generator的執行器
* 可是使用generator里的Yieldable支持的所有形式(supports thunks, promises, etc)
* 返回的結果是promise
那么它的用途就很明顯了
* 所有出現generator的地方它都可以出現
* 在以后的async/await里await后面可以接promise
## 源碼解析
co@4.6版本不到240行代碼,整體來說,還算比較簡單。但并不容易閱讀
~~~
// 核心代碼
function co(gen) {
// 緩存this
var ctx = this;
var args = slice.call(arguments, 1)
// we wrap everything in a promise to avoid promise chaining,
// which leads to memory leak errors.
// see https://github.com/tj/co/issues/180
// 重點,co的返回值是Promise對象。為什么可以then和catch的根源
return new Promise(function(resolve, reject) {
// 如果你懂Promise規范,就知道這是解決狀態回調,這是首次調用
onFulfilled();
/**
* @param {Mixed} res
* @return {Promise}
* @api private
*/
function onFulfilled(res) {
var ret;
try {
ret = gen.next(res);
} catch (e) {
return reject(e);
}
next(ret);
}
/**
* @param {Error} err
* @return {Promise}
* @api private
*/
// 如果你懂Promise規范,就知道這是拒絕狀態回調
function onRejected(err) {
var ret;
try {
ret = gen.throw(err);
} catch (e) {
return reject(e);
}
next(ret);
}
// generator執行器
// 如果ret.done,返回ret.value
// 否則,
function next(ret) {
// 如果執行完成,直接調用resolve把promise置為成功狀態
if (ret.done) return resolve(ret.value);
// 把yield的值轉換成promise
// 支持 promise,generator,generatorFunction,array,object
// toPromise的實現可以先不管,只要知道是轉換成promise就行了
var value = toPromise.call(ctx, ret.value);
// 成功轉換就可以直接給新的promise添加onFulfilled, onRejected。當新的promise狀態變成結束態(成功或失敗)。就會調用對應的回調。整個next鏈路就執行下去了。
// 為什么generator可以無限的next下去呢?
// return value.then(onFulfilled, onRejected);意味著,又要執行onFulfilled了
// onFulfilled里調用next(ret);
if (value && isPromise(value)) return value.then(onFulfilled, onRejected);
// 如果以上情況都沒發生,報錯
return onRejected(new TypeError('You may only yield a function, promise, generator, array, or object, '
+ 'but the following object was passed: "' + String(ret.value) + '"'));
}
});
}
~~~
讀此源碼要點
* 必須深刻理解Promise實現,知道構造函數里的onFulfilled和onRejected是什么意思
* 必須了解generator的執行機制,理解迭代器里的next以及next的返回對象{value:'',done: true}
核心代碼入口是onFulfilled,無論如何第一次的next(ret)是一定要執行的,因為generator必須要next()一下的。
所以next(ret)一定是重點,而且我們看onFulfilled和onRejected里都調用它,也就是所有的邏輯都會丟在這個next(ret)方法里。它實際上是一個狀態機的簡單實現。
~~~
// generator執行器
// 如果ret.done,返回ret.value
// 否則,
function next(ret) {
// 如果執行完成,直接調用resolve把promise置為成功狀態
if (ret.done) return resolve(ret.value);
// 把yield的值轉換成promise
// 支持 promise,generator,generatorFunction,array,object
// toPromise的實現可以先不管,只要知道是轉換成promise就行了
var value = toPromise.call(ctx, ret.value);
// 成功轉換就可以直接給新的promise添加onFulfilled, onRejected。當新的promise狀態變成結束態(成功或失敗)。就會調用對應的回調。整個next鏈路就執行下去了。
// 為什么generator可以無限的next下去呢?
// return value.then(onFulfilled, onRejected);意味著,又要執行onFulfilled了
// onFulfilled里調用next(ret);
if (value && isPromise(value)) return value.then(onFulfilled, onRejected);
// 如果以上情況都沒發生,報錯
return onRejected(new TypeError('You may only yield a function, promise, generator, array, or object, '
+ 'but the following object was passed: "' + String(ret.value) + '"'));
}
~~~
情景1: 狀態完成
~~~
// 如果執行完成,直接調用resolve把promise置為成功狀態
if (ret.done) return resolve(ret.value);
~~~
情景2: next,跳回onFulfilled,遞歸
~~~
// 成功轉換就可以直接給新的promise添加onFulfilled, onRejected。當新的promise狀態變成結束態(成功或失敗)。就會調用對應的回調。整個next鏈路就執行下去了。
// 為什么generator可以無限的next下去呢?
// return value.then(onFulfilled, onRejected);意味著,又要執行onFulfilled了
// onFulfilled里調用next(ret);
if (value && isPromise(value)) return value.then(onFulfilled, onRejected);
~~~
情景3: 捕獲異常
~~~
// 如果以上情況都沒發生,報錯
return onRejected(new TypeError('You may only yield a function, promise, generator, array, or object, '
+ 'but the following object was passed: "' + String(ret.value) + '"'));
~~~
以上是核心代碼說明。之前我們講了co實際有2種api,有參數和無參數的,很明顯以上是無參數的generator執行器,那么有參數的wrap呢?
~~~
// 為有參數的generator調用,提供簡單包裝
co.wrap = function (fn) {
createPromise.__generatorFunction__ = fn;
return createPromise;
function createPromise() {
// 重點,把arguments給fn當參數。
// call和apply是常規js api
return co.call(this, fn.apply(this, arguments));
}
};
~~~
通過call和apply組合使用,知識點比較簡單,但這樣用還是挺巧妙的。
其他的就基本是工具類了,其實也挺有意思的,自己看吧
## co引出的“血案”
ES6的Generator本意是為了計算而設計的迭代器,但tj覺得它可以用于流程控制,于是就有了co,co的歷史可以說經歷了目前所有的流程控制方案,而且由于支持Generator和yield就導致yieldable。
實際上co和Generator是把雙刃劍,給了我們強大便利的同時,也增加了非常多的概念,可能是過渡性的,也可能是過時的。
可是,你真的需要了解這么多么?從學習的角度,當然是多多意義,如果從實用的角度看,你可能不需要。
存在即合理,那么我們就看看這“血案”吧:
* 學習ES6的Generator
* 了解ES6的迭代器和迭代器相關的2種協議,了解for-of
* 了解co和co的2種用法,源碼
* 了解yieldable 5種(包括不常用Thunk)
* 如果是koa,還需要了解convert和compose
- 前言
- 1 skill
- 1.1 Coding WebIDE
- 1.2 git
- 1.3 extra practice
- 1.4 預習
- 2 nodejs入門
- 2.1 入門
- 2.2 安裝
- 2.3 helloworld
- 2.4 commonJS規范
- 2.5 模塊導出
- 2.6 Nodejs代碼調試
- 2.7 編寫Nodejs模塊
- 2.8 最小化問題
- 2.9 隨堂練習
- 3 異步流程控制
- 3.1 什么時候會用到異步流程控制
- 3.2 簡單做法async模塊
- 3.3 Promise/a+規范
- 3.4 Node.js Promise/a+實現
- 3.5 生成器Generators/yield
- 3.6 Async函數/Await
- 3.7 神奇的co
- 3.8 5種 yieldable
- 3.9 學習重點
- 3.10 隨堂練習
- 4 express和微信開發入門
- 4.1 入門
- 4.2 connect
- 4.3 靜態Http服務器
- 4.4 那些預處理器
- 4.5 路由
- 4.6 視圖與模塊引擎
- 4.7 中間件
- 4.8 更多實踐
- 4.9 微信入門
- 4.10 隨堂練習:完成登錄、注冊功能
- 5 微信實例與H5實踐
- 5.1 微信基礎和sandbox
- 5.2 公眾號菜單和自動回復
- 5.3 微信OAuth用戶授權
- 5.4 微信分享
- 5.5 wechat-api
- 5.6 H5-上篇
- 5.7 H5-下篇
- 5.8 隨堂練習
- 6 weui實戰
- 6.1 使用bower
- 6.2 移動端抽象
- 6.3 優化滑動列表
- 6.4 weui
- 6.5 讓weui和iscroll結婚
- 6.6 優化事件
- 6.7 how-to-write-h5
- 6.8 優化無止境
- 6.9 隨堂練習
- 7 微信支付
- 7.1 吹個牛
- 7.2 支付概述
- 7.3 科普幾個概念
- 7.4 準備
- 7.5 調試
- 7.6 公眾號支付(JSAPI)
- 7.7 對賬單
- 7.8 數據處理
- 7.9 隨堂練習
- 8 項目實戰《付費課程系統MVP》
- 8.1 需求分析
- 8.2 ui/ue
- 8.3 技術棧
- 8.4 模型
- 8.5 靜態api
- 8.6 開發
- 8.7 部署
- 8.8 監控
- 8.9 數據統計
- 8.10 demo
- 9 高級篇
- 9.1 前后端分離實踐?
- 9.2 如何展望未來的大前端
- 9.3 容器和微服務
- 10 答疑問題收集