摘要:參考文檔升級后的函數(shù)回調(diào)參數(shù)問題中的使用方法和還是不一樣的源碼講解的內(nèi)部機制優(yōu)化相關(guān)內(nèi)容文章官方文檔簡述使用過的都知道這個方法的作用,通過該方法會讓形式的函數(shù)風(fēng)格轉(zhuǎn)換成方法,可以認為是一顆語法糖,例如接下來我們就分析一下這個的內(nèi)部流程。
參考文檔
升級bluebird 3后Promise.promisify的函數(shù)回調(diào)參數(shù)問題:3中的使用方法和2還是不一樣的
How does Bluebird promisify work?:源碼講解promiify的內(nèi)部機制;
Optimizing for V8 - Inlining, Deoptimizations:V8優(yōu)化相關(guān)內(nèi)容文章
Promise.promisify:官方API文檔
1. 簡述使用過 Bluebird 的都知道 promisify 這個方法的作用,通過該方法會讓 NodeJS 形式的函數(shù)風(fēng)格轉(zhuǎn)換成 Promise 方法,可以認為是一顆 語法糖,例如:
var readFile = Promise.promisify(require("fs").readFile); readFile("myfile.js", "utf8").then(function(contents) { return eval(contents); }).then(function(result){ // other code })
接下來我們就分析一下這個 promisify 的內(nèi)部流程。下文,我們將以如下的代碼片段作為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},
本質(zhì)是調(diào)用 makeNodePromisifiedEval 或者是 makeNodePromisifiedClosure 方法,根據(jù) canEvaluate 變量選擇,該變量是在文件 ./util.js 中定義的,看源碼也很快能發(fā)現(xiàn)就一句話 var canEvaluate = typeof navigator == "undefined"; navigator 包含有關(guān)訪問者瀏覽器的信息,這里主要是區(qū)分是否是Node環(huán)境;
在 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 的參數(shù)可以在 升級bluebird 3后Promise.promisify的函數(shù)回調(diào)參數(shù)問題 中找到示例;
canEvaluate為true表示在Node環(huán)境,否則在瀏覽器環(huán)境;首先我們看在瀏覽器端的實現(xiàn) makeNodePromisifiedClosure
2.1、makeNodePromisifiedClosure相應(yīng)的源代碼是:(方便閱讀也寫上相關(guān)的注釋)
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 方法添加棧跟蹤,方便調(diào)試; promise._captureStackTrace(); // 獲取回調(diào)函數(shù)的定義:如果是方法名就調(diào)用this[method],否則直接調(diào)用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 方法相當于工廠函數(shù),你可以想象成是 某種類型的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; }; }
這個方法返回的是一個函數(shù) function(err,value){....},仔細想想,這種風(fēng)格是不是 node回調(diào)方法的風(fēng)格 ?這不但解釋了這也就解釋了 nodebackForPromise 名字的來歷,也解釋了 promisify 方法只能對 node異步函數(shù)(比如fs.readFile等)有效;
nodebackForPromise 其中的邏輯就比較簡單了,如果有錯誤就調(diào)用promise._reject,成功就調(diào)用promise._fulfill,這里也包含了 multiArgs 參數(shù)的處理,如果返回多個參數(shù),就把多個參數(shù)整合成數(shù)組形式;
好了,我們回到主流程,代碼執(zhí)行到 nodebackForPromise 這一行仍然還沒有對我們傳入的 callback 方法做特殊處理;
直到 cb.apply(_receiver, withAppended(arguments, fn));
這里的withAppended方法定義在 ./util.js中,是一個純函數(shù),用于拼接數(shù)組的,因此withAppended(arguments, fn)僅僅是給現(xiàn)有的入?yún)U展一個node回調(diào)風(fēng)格的fn;
在我們的 demo 里:
var promisifiedRead = Promise.promisify(fs.readFile); promisifiedRead("/etc/profile")
執(zhí)行到這里,實質(zhì)上就是執(zhí)行 fs.readFile.apply(this,"/etc/profile",fn),是不是就很清晰了,其實和原有的調(diào)用方式是一樣的!僅僅是在 fn 中加入了promise功能;那么一旦 fs.readFile 執(zhí)行完成,之后就會調(diào)用 fn 方法,也就進入了promise的世界了; 棒棒噠!
2.2、makeNodePromisifiedEval其實上述解讀了 makeNodePromisifiedClosure 方法相信已經(jīng)了解了 promisify 這種魔法的本質(zhì),這節(jié)要講的 makeNodePromisifiedEval 的操作流程也是類似的;
只是因為運行在 node 端,可以 利用V8引擎優(yōu)化性能,利用其 function inlining 特性,在調(diào)用callback 方法時 極大地節(jié)約創(chuàng)建閉包的成本;
可通過google搜索 v8 函數(shù)內(nèi)聯(lián) 來查閱更多資料;
內(nèi)聯(lián)化對 callback.apply 方法是 不起作用的,除非它調(diào)用的是 arguments 參數(shù),而上面我們也看到了,這個參數(shù)我們使用 withAppended(arguments, fn),返回的是一個新的參數(shù)數(shù)組,因此內(nèi)聯(lián)優(yōu)化是不起作用的;
與此相對應(yīng)的,callback.call方法可以被內(nèi)聯(lián)優(yōu)化;call 和 apply 方法的區(qū)別在于,apply接受一個數(shù)組作為參數(shù),而call 必須詳細指定每一個參數(shù)(也正是如此,可以用于內(nèi)聯(lián)優(yōu)化);makeNodePromisifiedEval正是將上述apply方法替換成call方法,以期望達到V8引擎最大的優(yōu)化性能 —— 因此必須讓引擎知道入?yún)€數(shù)總數(shù)
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); };
為了能依據(jù)不同的callback構(gòu)造不同的內(nèi)聯(lián)方法,makeNodePromisifiedEval 使用了 原始函數(shù)構(gòu)造器,該函數(shù)構(gòu)造器的參數(shù)起于 Promise 終于 INTERNAL;
body變量中就是真正的函數(shù)體了,你可以發(fā)現(xiàn)其中大部分的代碼和 makeNodePromisifiedClosure 方法是一樣的,僅僅不一樣的是多了一節(jié) CodeForSwitchCase,用于針對不同的入?yún)€數(shù)產(chǎn)生不同的 .call 函數(shù)調(diào)用;
這里的generateArgumentSwitchCase函數(shù)比較復(fù)雜,這里就不展開了,總之會最后會產(chǎn)生類似如下的代碼:
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. 總結(jié)
暫無,閱讀源碼筆記
下面的是我的公眾號二維碼圖片,歡迎關(guān)注。
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/81757.html
摘要:控制臺將顯示回調(diào)地獄通常,回調(diào)只能由一個異步函數(shù)調(diào)用。更多資源使更友好規(guī)范使用異步函數(shù)簡化異步編碼旅程異步編程是一項在中無法避免的挑戰(zhàn)。 JavaScript經(jīng)常聲稱是_異步_。那是什么意思?它如何影響發(fā)展?近年來這種方法有何變化? 請思考以下代碼: result1 = doSomething1(); result2 = doSomething2(result1); 大多數(shù)語言都處理每...
摘要:控制臺將顯示回調(diào)地獄通常,回調(diào)只能由一個異步函數(shù)調(diào)用。更多資源使更友好規(guī)范使用異步函數(shù)簡化異步編碼旅程異步編程是一項在中無法避免的挑戰(zhàn)。 JavaScript經(jīng)常聲稱是_異步_。那是什么意思?它如何影響發(fā)展?近年來這種方法有何變化? 請思考以下代碼: result1 = doSomething1(); result2 = doSomething2(result1); 大多數(shù)語言都處理每...
摘要:控制臺將顯示回調(diào)地獄通常,回調(diào)只能由一個異步函數(shù)調(diào)用。更多資源使更友好規(guī)范使用異步函數(shù)簡化異步編碼旅程異步編程是一項在中無法避免的挑戰(zhàn)。 JavaScript經(jīng)常聲稱是_異步_。那是什么意思?它如何影響發(fā)展?近年來這種方法有何變化? 請思考以下代碼: result1 = doSomething1(); result2 = doSomething2(result1); 大多數(shù)語言都處理每...
摘要:自定義的化有那么一些場景,是不能夠直接使用來進行轉(zhuǎn)換的,有大概這么兩種情況沒有遵循約定的回調(diào)函數(shù)返回多個參數(shù)的回調(diào)函數(shù)首先是第一個,如果沒有遵循我們的約定,很可能導(dǎo)致的誤判,得不到正確的反饋。 util.promisify是在node.js 8.x版本中新增的一個工具,用于將老式的Error first callback轉(zhuǎn)換為Promise對象,讓老項目改造變得更為輕松。 在官方推...
摘要:例如,的回調(diào)函數(shù)包含下面幾個參數(shù)轉(zhuǎn)換成之后,它的參數(shù)將會變成這樣一個對象通過內(nèi)部符號處理非標準回調(diào)函數(shù)。 Nodejs 8 有一個新的工具函數(shù) util.promisify()。他將一個接收回調(diào)函數(shù)參數(shù)的函數(shù)轉(zhuǎn)換成一個返回Promise的函數(shù)。 1、util.promisify()小例子 如果你給以下命令傳入文件路徑,則會輸出文件內(nèi)容 // echo.js const {promis...
閱讀 2228·2019-08-30 15:54
閱讀 1964·2019-08-30 13:49
閱讀 683·2019-08-29 18:44
閱讀 836·2019-08-29 18:39
閱讀 1119·2019-08-29 15:40
閱讀 1540·2019-08-29 12:56
閱讀 3155·2019-08-26 11:39
閱讀 3107·2019-08-26 11:37