Promisifyのソースコード解析


参考文献
  • bluebird 3をアップグレードした後、Promise.promisifyの関数コールパラメータの問題:3の使用方法と2の違いは
  • です.
  • How does Bluebird promisify work?ソースコードはpromiifyの内部メカニズムを説明します.
  • Optimizing for V 8-Inlinng、Deoptimizations:V 8最適化関連内容文章
  • 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の内部プロセスを分析します.以下、下記のコードセグメントをデモとして説明します.
    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を呼び出す方法であり、canEvaalute変数によって選択され、この変数はファイル/util.jsで定義されています.ソースを見てもすぐに発見できます.
  • 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() {
        //...
    });
    var canEvaluate = typeof navigator == "undefined";のパラメータは、bluebird 3をアップグレードした後にPromise.promisifyの関数コールパラメータ問題において一例を見つけることができる.multiはtrueでNode環境を表しています.そうでないとブラウザ環境にあります.まず、ブラウザの端にあるmakeNodePromified Cloosureの実現を見ます.
    2.1、makeNodePromified Cloosure
    ソースコードに対応しています.
    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;
    }
    
    ここのcanEvaluateの方法は工場の関数に相当しています.何かの種類のプロミセジェネレータだと想像できます.この名前の中のnodebackの単語はとても不思議ですか?ソースコードを見たら、はっと悟ると思います.ハハ、ソースコードを見てみます.
    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非同期関数(例えばnodebackForPromiseなど)に対してのみ有効であると説明した.
    nodebackForPromiseの中のロジックは比較的簡単で、もしエラーがあればfs.readFileを呼び出し、promise._rejectを呼び出すことに成功しました.ここにmultiArgsパラメータの処理も含まれています.複数のパラメータを返すと、複数のパラメータを配列形式に統合します.
    はい、メインフローに戻ります.コードはnodebackForPromiseに実行します.まだ私達が入ってきたpromise._fulfill方法に対して特別な処理をしていません.callbackまで
    ここでのcb.apply(_receiver, withAppended(arguments, fn));方法は.util.jsにおいて、スティッチング配列のための純粋な関数であると定義されているので、withAppendedは、既存のエントリに対して、nodeコールスタイルを拡張するwithAppended(arguments, fn)だけである.
    私たちのデモの中で:
    var promisifiedRead = Promise.promisify(fs.readFile);
    promisifiedRead('/etc/profile')
    ここまで実行して、実質的にfnを実行して、とてもはっきりしているのではありませんか?fnにpromise機能を追加しただけです.fs.readFileが実行されれば、fs.readFile.apply(this,'/etc/profile',fn)方法を呼び出し、promiseの世界に入ります.バンバン
    2.2、makeNodePromified Everal
    実は上記のように、fn方法を解読しました.プロモーションという魔法の本質を理解したと信じています.これはmakeNodePromisifiedClosureの動作フローも同様です.
    ただし、node端で動作するので、V 8エンジンを利用して性能を最適化し、そのfunction inlining特性を利用して、makeNodePromisifiedEval方法を呼び出した時に、クローズドを作成するコストを大幅に節約することができる.
    googleで検索できます.
    v 8関数のインラインはより多くの資料を調べます.
    インライン化はcallback方法に対して機能しません.アーグメンントパラメータで起動していない限り、上記のパラメータも見ました.このパラメータはcallback.applyを使って戻ります.新しいパラメータ配列ですので、インライン最適化は機能しません.
    これに対応して、withAppended(arguments, fn)方法をインラインで最適化することができる.callback.callcallの方法の違いは、applyはパラメータとして配列を受け入れ、callは各パラメータを詳細に指定しなければならないことである.applyは、上記makeNodePromisifiedEval方法をapply方法に置き換え、V 8エンジンの最大の最適化性能を達成することを期待している.したがって、エンジンに参加総数を認識させなければならない.
    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); };
    異なるcalback構造に従って異なるインライン法を構築するために、callは、元の関数コンストラクタを使用して、この関数コンストラクタのパラメータは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.まとめ
    まだないです.ソースのメモを読みます.
    下のは私の公衆番号の二次元コード写真です.ご注意ください.