成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

Promisify 的源碼解析

gougoujiang / 443人閱讀

摘要:參考文檔升級后的函數(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)化;callapply 方法的區(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

相關(guān)文章

  • 現(xiàn)代JS中流程控制:詳解Callbacks 、Promises 、Async/Await

    摘要:控制臺將顯示回調(diào)地獄通常,回調(diào)只能由一個異步函數(shù)調(diào)用。更多資源使更友好規(guī)范使用異步函數(shù)簡化異步編碼旅程異步編程是一項在中無法避免的挑戰(zhàn)。 JavaScript經(jīng)常聲稱是_異步_。那是什么意思?它如何影響發(fā)展?近年來這種方法有何變化? 請思考以下代碼: result1 = doSomething1(); result2 = doSomething2(result1); 大多數(shù)語言都處理每...

    shadowbook 評論0 收藏0
  • 現(xiàn)代JS中流程控制:詳解Callbacks 、Promises 、Async/Await

    摘要:控制臺將顯示回調(diào)地獄通常,回調(diào)只能由一個異步函數(shù)調(diào)用。更多資源使更友好規(guī)范使用異步函數(shù)簡化異步編碼旅程異步編程是一項在中無法避免的挑戰(zhàn)。 JavaScript經(jīng)常聲稱是_異步_。那是什么意思?它如何影響發(fā)展?近年來這種方法有何變化? 請思考以下代碼: result1 = doSomething1(); result2 = doSomething2(result1); 大多數(shù)語言都處理每...

    oujie 評論0 收藏0
  • 現(xiàn)代JS中流程控制:詳解Callbacks 、Promises 、Async/Await

    摘要:控制臺將顯示回調(diào)地獄通常,回調(diào)只能由一個異步函數(shù)調(diào)用。更多資源使更友好規(guī)范使用異步函數(shù)簡化異步編碼旅程異步編程是一項在中無法避免的挑戰(zhàn)。 JavaScript經(jīng)常聲稱是_異步_。那是什么意思?它如何影響發(fā)展?近年來這種方法有何變化? 請思考以下代碼: result1 = doSomething1(); result2 = doSomething2(result1); 大多數(shù)語言都處理每...

    anquan 評論0 收藏0
  • util.promisify 那些事兒

    摘要:自定義的化有那么一些場景,是不能夠直接使用來進行轉(zhuǎn)換的,有大概這么兩種情況沒有遵循約定的回調(diào)函數(shù)返回多個參數(shù)的回調(diào)函數(shù)首先是第一個,如果沒有遵循我們的約定,很可能導(dǎo)致的誤判,得不到正確的反饋。 util.promisify是在node.js 8.x版本中新增的一個工具,用于將老式的Error first callback轉(zhuǎn)換為Promise對象,讓老項目改造變得更為輕松。 在官方推...

    shuibo 評論0 收藏0
  • [譯] Node.js 8: util.promisify()

    摘要:例如,的回調(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...

    Shimmer 評論0 收藏0

發(fā)表評論

0條評論

最新活動
閱讀需要支付1元查看
<