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