摘要:參考文檔升級后的函數回調參數問題中的使用方法和還是不一樣的源碼講解的內部機制優化相關內容文章官方文檔簡述使用過的都知道這個方法的作用,通過該方法會讓形式的函數風格轉換成方法,可以認為是一顆語法糖,例如接下來我們就分析一下這個的內部流程。
參考文檔
升級bluebird 3后Promise.promisify的函數回調參數問題:3中的使用方法和2還是不一樣的
How does Bluebird promisify work?:源碼講解promiify的內部機制;
Optimizing for V8 - Inlining, Deoptimizations:V8優化相關內容文章
Promise.promisify:官方API文檔
1. 簡述使用過 Bluebird 的都知道 promisify 這個方法的作用,通過該方法會讓 NodeJS 形式的函數風格轉換成 Promise 方法,可以認為是一顆 語法糖,例如:
var readFile = Promise.promisify(require("fs").readFile); readFile("myfile.js", "utf8").then(function(contents) { return eval(contents); }).then(function(result){ // other code })
接下來我們就分析一下這個 promisify 的內部流程。下文,我們將以如下的代碼片段作為demo來講解
var Promise = require("bluebird"); var fs = require("fs"); // this is how you read a file without promisify fs.readFile("/etc/profile", function(err, buffer) { console.log("fs.readFile: " + buffer.toString()); }); // this is the promisified version var promisifiedRead = Promise.promisify(fs.readFile); promisifiedRead("/etc/profile") .then(function(buffer) { console.log("promisified readFile: " + buffer.toString()); });2. 開始剖析
在文件 promisify.js 中:
var makeNodePromisified = canEvaluate ? makeNodePromisifiedEval : makeNodePromisifiedClosure; .... function promisify(callback, receiver, multiArgs) { return makeNodePromisified(callback, receiver, undefined, callback, null, multiArgs); } Promise.promisify = function (fn, options) { if (typeof fn !== "function") { throw new TypeError("expecting a function but got " + util.classString(fn)); } if (isPromisified(fn)) { return fn; } options = Object(options); var receiver = options.context === undefined ? THIS : options.context; var multiArgs = !!options.multiArgs; var ret = promisify(fn, receiver, multiArgs); util.copyDescriptors(fn, ret, propsFilter); return ret; };
options 的最基本形式是 {context:this,multiArgs:false},
本質是調用 makeNodePromisifiedEval 或者是 makeNodePromisifiedClosure 方法,根據 canEvaluate 變量選擇,該變量是在文件 ./util.js 中定義的,看源碼也很快能發現就一句話 var canEvaluate = typeof navigator == "undefined"; navigator 包含有關訪問者瀏覽器的信息,這里主要是區分是否是Node環境;
在 Promise.promisify 官方API文檔中有講過,context就是需要綁定的上下文對象:
var redisGet = Promise.promisify(redisClient.get, {context: redisClient}); redisGet("foo").then(function() { //... });
也可以這么寫:
var getAsync = Promise.promisify(redisClient.get); getAsync.call(redisClient, "foo").then(function() { //... });
而 multi 的參數可以在 升級bluebird 3后Promise.promisify的函數回調參數問題 中找到示例;
canEvaluate為true表示在Node環境,否則在瀏覽器環境;首先我們看在瀏覽器端的實現 makeNodePromisifiedClosure
2.1、makeNodePromisifiedClosure相應的源代碼是:(方便閱讀也寫上相關的注釋)
function makeNodePromisifiedClosure(callback, receiver, _, fn, __, multiArgs) { var defaultThis = (function() {return this;})(); var method = callback; if (typeof method === "string") { callback = fn; } function promisified() { var _receiver = receiver; if (receiver === THIS) _receiver = this; var promise = new Promise(INTERNAL); // _captureStackTrace 方法添加棧跟蹤,方便調試; promise._captureStackTrace(); // 獲取回調函數的定義:如果是方法名就調用this[method],否則直接調用callback var cb = typeof method === "string" && this !== defaultThis ? this[method] : callback; var fn = nodebackForPromise(promise, multiArgs); try { cb.apply(_receiver, withAppended(arguments, fn)); } catch(e) { promise._rejectCallback(maybeWrapAsError(e), true, true); } if (!promise._isFateSealed()) promise._setAsyncGuaranteed(); return promise; } util.notEnumerableProp(promisified, "__isPromisified__", true); return promisified; }
這里的 nodebackForPromise 方法相當于工廠函數,你可以想象成是 某種類型的promise生成器,這個名字里的 nodeback 單詞是不是很讓你莫名奇妙?,不過相信看了源碼會讓你恍然大悟的,哈哈,我們看一下它的源碼(在 ./nodeback.js 文件中)
function nodebackForPromise(promise, multiArgs) { return function(err, value) { if (promise === null) return; if (err) { var wrapped = wrapAsOperationalError(maybeWrapAsError(err)); promise._attachExtraTrace(wrapped); promise._reject(wrapped); } else if (!multiArgs) { promise._fulfill(value); } else { INLINE_SLICE(args, arguments, 1); promise._fulfill(args); } promise = null; }; }
這個方法返回的是一個函數 function(err,value){....},仔細想想,這種風格是不是 node回調方法的風格 ?這不但解釋了這也就解釋了 nodebackForPromise 名字的來歷,也解釋了 promisify 方法只能對 node異步函數(比如fs.readFile等)有效;
nodebackForPromise 其中的邏輯就比較簡單了,如果有錯誤就調用promise._reject,成功就調用promise._fulfill,這里也包含了 multiArgs 參數的處理,如果返回多個參數,就把多個參數整合成數組形式;
好了,我們回到主流程,代碼執行到 nodebackForPromise 這一行仍然還沒有對我們傳入的 callback 方法做特殊處理;
直到 cb.apply(_receiver, withAppended(arguments, fn));
這里的withAppended方法定義在 ./util.js中,是一個純函數,用于拼接數組的,因此withAppended(arguments, fn)僅僅是給現有的入參擴展一個node回調風格的fn;
在我們的 demo 里:
var promisifiedRead = Promise.promisify(fs.readFile); promisifiedRead("/etc/profile")
執行到這里,實質上就是執行 fs.readFile.apply(this,"/etc/profile",fn),是不是就很清晰了,其實和原有的調用方式是一樣的!僅僅是在 fn 中加入了promise功能;那么一旦 fs.readFile 執行完成,之后就會調用 fn 方法,也就進入了promise的世界了; 棒棒噠!
2.2、makeNodePromisifiedEval其實上述解讀了 makeNodePromisifiedClosure 方法相信已經了解了 promisify 這種魔法的本質,這節要講的 makeNodePromisifiedEval 的操作流程也是類似的;
只是因為運行在 node 端,可以 利用V8引擎優化性能,利用其 function inlining 特性,在調用callback 方法時 極大地節約創建閉包的成本;
可通過google搜索 v8 函數內聯 來查閱更多資料;
內聯化對 callback.apply 方法是 不起作用的,除非它調用的是 arguments 參數,而上面我們也看到了,這個參數我們使用 withAppended(arguments, fn),返回的是一個新的參數數組,因此內聯優化是不起作用的;
與此相對應的,callback.call方法可以被內聯優化;call 和 apply 方法的區別在于,apply接受一個數組作為參數,而call 必須詳細指定每一個參數(也正是如此,可以用于內聯優化);makeNodePromisifiedEval正是將上述apply方法替換成call方法,以期望達到V8引擎最大的優化性能 —— 因此必須讓引擎知道入參個數總數
makeNodePromisifiedEval = function(callback, receiver, originalName, fn, _, multiArgs) { var newParameterCount = Math.max(0, parameterCount(fn) - 1); var body = ""use strict"; var ret = function (Parameters) { "use strict"; var len = arguments.length; var promise = new Promise(INTERNAL); promise._captureStackTrace(); var nodeback = nodebackForPromise(promise, " + multiArgs + "); var ret; var callback = tryCatch(fn); switch(len) { [CodeForSwitchCase] } if (ret === errorObj) { promise._rejectCallback(maybeWrapAsError(ret.e), true, true); } if (!promise._isFateSealed()) promise._setAsyncGuaranteed(); return promise; }; notEnumerableProp(ret, "__isPromisified__", true); return ret; ".replace("[CodeForSwitchCase]", generateArgumentSwitchCase()) .replace("Parameters", parameterDeclaration(newParameterCount)); return new Function("Promise", "fn", "receiver", "withAppended", "maybeWrapAsError", "nodebackForPromise", "tryCatch", "errorObj", "notEnumerableProp", "INTERNAL", body)(Promise, fn, receiver, withAppended, maybeWrapAsError, nodebackForPromise, util.tryCatch, util.errorObj, util.notEnumerableProp, INTERNAL); };
為了能依據不同的callback構造不同的內聯方法,makeNodePromisifiedEval 使用了 原始函數構造器,該函數構造器的參數起于 Promise 終于 INTERNAL;
body變量中就是真正的函數體了,你可以發現其中大部分的代碼和 makeNodePromisifiedClosure 方法是一樣的,僅僅不一樣的是多了一節 CodeForSwitchCase,用于針對不同的入參個數產生不同的 .call 函數調用;
這里的generateArgumentSwitchCase函數比較復雜,這里就不展開了,總之會最后會產生類似如下的代碼:
switch(len) { case 2:ret = callback.call(this, _arg0, _arg1, nodeback); break; case 1:ret = callback.call(this, _arg0, nodeback); break; case 0:ret = callback.call(this, nodeback); break; case 3:ret = callback.call(this, _arg0, _arg1, _arg2, nodeback); break;3. 總結
暫無,閱讀源碼筆記
下面的是我的公眾號二維碼圖片,歡迎關注。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/81757.html
摘要:控制臺將顯示回調地獄通常,回調只能由一個異步函數調用。更多資源使更友好規范使用異步函數簡化異步編碼旅程異步編程是一項在中無法避免的挑戰。 JavaScript經常聲稱是_異步_。那是什么意思?它如何影響發展?近年來這種方法有何變化? 請思考以下代碼: result1 = doSomething1(); result2 = doSomething2(result1); 大多數語言都處理每...
摘要:控制臺將顯示回調地獄通常,回調只能由一個異步函數調用。更多資源使更友好規范使用異步函數簡化異步編碼旅程異步編程是一項在中無法避免的挑戰。 JavaScript經常聲稱是_異步_。那是什么意思?它如何影響發展?近年來這種方法有何變化? 請思考以下代碼: result1 = doSomething1(); result2 = doSomething2(result1); 大多數語言都處理每...
摘要:控制臺將顯示回調地獄通常,回調只能由一個異步函數調用。更多資源使更友好規范使用異步函數簡化異步編碼旅程異步編程是一項在中無法避免的挑戰。 JavaScript經常聲稱是_異步_。那是什么意思?它如何影響發展?近年來這種方法有何變化? 請思考以下代碼: result1 = doSomething1(); result2 = doSomething2(result1); 大多數語言都處理每...
摘要:自定義的化有那么一些場景,是不能夠直接使用來進行轉換的,有大概這么兩種情況沒有遵循約定的回調函數返回多個參數的回調函數首先是第一個,如果沒有遵循我們的約定,很可能導致的誤判,得不到正確的反饋。 util.promisify是在node.js 8.x版本中新增的一個工具,用于將老式的Error first callback轉換為Promise對象,讓老項目改造變得更為輕松。 在官方推...
摘要:例如,的回調函數包含下面幾個參數轉換成之后,它的參數將會變成這樣一個對象通過內部符號處理非標準回調函數。 Nodejs 8 有一個新的工具函數 util.promisify()。他將一個接收回調函數參數的函數轉換成一個返回Promise的函數。 1、util.promisify()小例子 如果你給以下命令傳入文件路徑,則會輸出文件內容 // echo.js const {promis...
閱讀 2211·2019-08-30 15:54
閱讀 1947·2019-08-30 13:49
閱讀 666·2019-08-29 18:44
閱讀 824·2019-08-29 18:39
閱讀 1104·2019-08-29 15:40
閱讀 1524·2019-08-29 12:56
閱讀 3134·2019-08-26 11:39
閱讀 3094·2019-08-26 11:37