摘要:模塊可以將異步解放成同步。源碼分析使用的模塊版本號為首先看一些用于判斷對象類型的函數對數組方法的引用這兩個應該就不用說了吧。。。看一下模塊的輸出部分因此以下三種用法等價接著就是重頭戲函數了。
寫在前面本文只在個人博客和 SegmentFault 社區個人專欄發表,轉載請注明出處
個人博客: https://zengxiaotao.github.io
SegmentFault 個人專欄: https://segmentfault.com/blog...
學 nodejs 當然避免不了學習框架,畢竟原生的 API 較底層。最先接觸的是 Koa 。看看官網的描述
next generation web framework for node.js
我翻譯一下就是: 基于 node.js 的下一代 web 開發框架。好像很厲害的樣子!koa 是一個輕量級的框架,本質上提供了一個架子,通過 各種中間件的級聯的方式實現特定的功能。koa 借助 promise 和 generator , 很好解決了異步組合問題。
那什么又是 co 。學習 koa 就一定少不了學習 co 模塊。co 模塊可以將異步解放成同步。co 函數接受一個 generator 函數作為參數,在函數內部自動執行 yield 。
co 源碼分析使用的 co 模塊版本號為 4.6.0
首先看一些用于判斷對象類型的函數
var slice = Array.prototype.slice; // 對數組 slice 方法的引用
function isObject(val) { return Object == val.constructor; }
這兩個應該就不用說了吧。。。
function isPromise(obj) { return "function" == typeof obj.then; }
判斷一個對象是否是一個 promise 實例,判斷的依據也很簡單,根據 “鴨子類型”,判斷這個對象是否有 then 方法
function isGenerator(obj) { return "function" == typeof obj.next && "function" == typeof obj.throw; }
類似的,判斷一個對象時候是 generator 實例,只需判斷這個對象是否具有 next 方法和 throw 方法。
function isGeneratorFunction(obj) { var constructor = obj.constructor; if (!constructor) return false; if ("GeneratorFunction" === constructor.name || "GeneratorFunction" === constructor.displayName) return true; return isGenerator(constructor.prototype); }
判斷是否是一個 generator 函數,只需判斷這個函數是否是 GeneratorFunction 函數的實例
以上所講的在之后將 value 包裝成 promise 實例時都會用到。
看一下 co 模塊的輸出部分
module.exports = co["default"] = co.co = co
因此以下三種用法等價
var co = require("co") // (1) var co = require("co").co // (2) var co = require("co").default // (3)
接著就是重頭戲 co 函數了。
function co(gen) { var ctx = this; // 保存函數的執行上下文對象 var args = slice.call(arguments, 1) // 傳給 gen 函數的參數 // 返回一個 promise 實例 return new Promise(function(resolve, reject) { // 根據傳入的 generator 函數生成一個 generator 實例 if (typeof gen === "function") gen = gen.apply(ctx, args); // 如果生成的 gen 不是一個 generator 實例, // promise 直接變成 resolved 狀態 if (!gen || typeof gen.next !== "function") return resolve(gen); // 執行 onFulfilled 方法 onFulfilled(); function onFulfilled(res) { var ret; try { // 執行 gen 的 next 方法 ret = gen.next(res); } catch (e) { return reject(e); } // 并將這個值傳入 next 函數 next(ret); } function onRejected(err) { var ret; try { ret = gen.throw(err); } catch (e) { return reject(e); } next(ret); } function next(ret) { // 如果 gen 執行完畢, ret.done 變為 true ,那么這個 promise 的實例 // 的狀態自然變成了 resolved if (ret.done) return resolve(ret.value); var value = toPromise.call(ctx, ret.value); // 將 value 重新包裝成一個 promise 實例 // 新返回的 promise 實例的 resolve 方法設置為 onFulfilled 函數,再次執行 next 方法, 從而實現了自動調用 generator 實例的 next 方法 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) + """)); } }); }
以上,就是 co 模塊就實現了自動執行 generator 實例的 next 方法。那么接下來看看 co 是怎么把一個值轉化為一個 promise 實例。
function toPromise(obj) { if (!obj) return obj; // 如果傳入的 obj 是假值,返回這個假值 如 undefined , false, null if (isPromise(obj)) return obj; // 如果是 Promise 實例,返回這個 promise 實例 if (isGeneratorFunction(obj) || isGenerator(obj)) return co.call(this, obj); // 如果是 generator 函數或者 一個generator if ("function" == typeof obj) return thunkToPromise.call(this, obj); // 如果是 thunk 函數 if (Array.isArray(obj)) return arrayToPromise.call(this, obj); // 如果是一個數組 if (isObject(obj)) return objectToPromise.call(this, obj); // 如果是一個 plain object return obj; // 如果是原始值,則返回這個原始值。 }
那么每個函數依次看下去。
function thunkToPromise(fn) { var ctx = this; // 保存函數上下文對象 // 返回一個 promise 實例 return new Promise(function (resolve, reject) { // 執行傳入的 thunk 函數 // thunk 函數接受一個 回調函數 作為參數 fn.call(ctx, function (err, res) { // 如果 thunk 函數運行錯誤 // promise 實例的 變為 rejected 狀態,執行 reject 函數,也就是 co 函數內定義的 onRejected 函數,下同 if (err) return reject(err); // 獲得多余參數 if (arguments.length > 2) res = slice.call(arguments, 1); // promise 狀態變為 resolved ,執行 resolve 函數,也就是 onFulfilled 函數 resolve(res); }); }); }
所以,總結一下就是說,如果 generator 里 yield 后面是一個 thunk 函數, 這個 thunk 函數接受一個回調參數作為參數,co 在這個回調函數里定義了何時將 promise 的狀態變為 resolved 或者 rejected ,
function arrayToPromise(obj) { // Promise.all 方法返回一個 新的 promise 實例 // 如果 obj 是一個數組,把每個元素包裝成一個 promise 實例 // 如果每一個 promise 如果都變為 resolved 狀態 // 那么返回的新的 promise 實例的狀態變為 resloved 狀態 // 傳給 resolve 函數的參數為之前每個 promise 的返回值所組成的數組 return Promise.all(obj.map(toPromise, this)); }
同樣,如果 obj 是一個數組,也就是 yield 語句后面的表達式的值為一個數組,那么就執行 Promise.all 方法, 將數組的每一項都變成一個 promise 實例。
具體方法如下:
使用 toPromise 方法將 obj 數組中的每一項都包裝成一個 promise 實例
如果上一步中的數組中有元素不是 promise 實例,Promise.all 方法將調用 Promise.resolve 方法,將其轉化為 promise 實例。
Promise.all 方法返回一個新的 promise 實例。
只有 promise 實例數組中的所有實例的狀態都變為 resolved 狀態時,這個新的 promise 實例的狀態才會變成 resolved。只要數組中有一個 promise 實例的狀態變為 rejected ,新的promise 實例狀態也馬上變為 rejected 。
當返回的新的 promise 實例狀態變為 resolved 時,傳入其 resolve 函數的參數為之前數組中每個 promise 實例調用 resolve 函數的返回值組成的數組。如果返回的新的 promise 的狀態變為 rejected ,那么傳給 reject 函數的參數為數組中的 promise 實例最先變為 rejected 狀態的那一個執行 reject 函數的返回值。
真繞口,多看幾遍應該就能理解了。
最后來看看如果 ret.value 如果是一個對象,co 模塊是怎么樣把它變成一個 promise 實例的。
function objectToPromise(obj){ // 定義一個空對象 var results = new obj.constructor(); // 獲取 obj 的全部屬性 var keys = Object.keys(obj); // 用于盛放 每個屬性值生成的對應的 promise 實例 var promises = []; for (var i = 0; i < keys.length; i++) { var key = keys[i]; var promise = toPromise.call(this, obj[key]); // 根據屬性值生成一個 promise 實例 if (promise && isPromise(promise)) defer(promise, key); else results[key] = obj[key]; } // 通過一個 promise.all 方法返回一個新的實例 return Promise.all(promises).then(function () { return results; // 將 results 作為 onFulfilled 函數的參數 }); // 函數的作用 // 給 promise 添加 resolve 函數 // 并且把這個 promise 實例推入 promises 數組 function defer(promise, key) { // predefine the key in the result results[key] = undefined; promises.push(promise.then(function (res) { results[key] = res; // 定義promise 實例的 resolve 函數 })); } }總結
分析完 co 的整個源碼總結一下整個執行的過程。首先,co 函數接受一個 generator 函數,并且在 co 函數內部執行,生成一個 generator 實例。調用 generator 的 next 方法, 對生成的對象的 value 屬性值使用 toPromise 方法,生成一個 promise 實例,當這個 promise 實例的狀態變為 resolved 時,執行 onFulfilled 方法,再次對 generator 實例執行 next 方法,然后重復整個過程。如果出現錯誤,則執行這個 promise 實例定義的 reject 函數即 onRejected 方法。
以上即實現了將異步過程同步化。
最后歡迎 star
https://github.com/zengxiaotao/zengxiaotao.github.io
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/91339.html
摘要:返回的結果是一個對象,類似于表示本次后面執行之后返回的結果。對象用于一個異步操作的最終完成或失敗及其結果值的表示簡單點說就是處理異步請求。源碼分析主要脈絡函數調用后,返回一個實例。參考鏈接解釋對象的用法的源碼及其用法 本文始發于我的個人博客,如需轉載請注明出處。為了更好的閱讀體驗,可以直接進去我的個人博客看。 前言 知識儲備 閱讀本文需要對Generator和Promise有一個基本的...
摘要:新聞熱點國內國外,前端最新動態發布近日,正式發布新版本中提供了一系列的特性與問題修復。而近日正式發布,其能夠幫助開發者快速構建應用。 前端每周清單第 10 期:Firefox53、React VR發布、JS測試技術概述、Microsoft Edge現代DOM樹構建及性能之道 為InfoQ中文站特供稿件,首發地址為這里;如需轉載,請與InfoQ中文站聯系。從屬于筆者的 Web 前端入門...
摘要:前端每周清單專注前端領域內容,以對外文資料的搜集為主,幫助開發者了解一周前端熱點分為新聞熱點開發教程工程實踐深度閱讀開源項目巔峰人生等欄目。對該漏洞的綜合評級為高危。目前,相關利用方式已經在互聯網上公開,近期出現攻擊嘗試爆發的可能。 前端每周清單專注前端領域內容,以對外文資料的搜集為主,幫助開發者了解一周前端熱點;分為新聞熱點、開發教程、工程實踐、深度閱讀、開源項目、巔峰人生等欄目。歡...
摘要:前端每周清單專注前端領域內容,以對外文資料的搜集為主,幫助開發者了解一周前端熱點分為新聞熱點開發教程工程實踐深度閱讀開源項目巔峰人生等欄目。它能夠為我們提供類似于預處理器命名空間等多方面的輔助。 前端每周清單專注前端領域內容,以對外文資料的搜集為主,幫助開發者了解一周前端熱點;分為新聞熱點、開發教程、工程實踐、深度閱讀、開源項目、巔峰人生等欄目。歡迎關注【前端之巔】微信公眾號(ID:f...
閱讀 2572·2021-09-23 11:21
閱讀 1882·2021-09-22 15:15
閱讀 970·2021-09-10 11:27
閱讀 3440·2019-08-30 15:54
閱讀 651·2019-08-30 15:52
閱讀 1335·2019-08-30 15:44
閱讀 2349·2019-08-29 15:06
閱讀 2972·2019-08-28 18:21