Promisifyのソースコード解析
9679 ワード
参考文献 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方法に変換します.
ファイル です.の本質は Promise.promisify公式APIドキュメントで述べたことがあります.contextはバインディングが必要なコンテキストオブジェクトです.
2.1、makeNodePromified Cloosure
ソースコードに対応しています.
nodebackForPromiseの中のロジックは比較的簡単で、もしエラーがあれば
はい、メインフローに戻ります.コードはnodebackForPromiseに実行します.まだ私達が入ってきた
ここでの
私たちのデモの中で:
2.2、makeNodePromified Everal
実は上記のように、
ただし、node端で動作するので、V 8エンジンを利用して性能を最適化し、そのfunction inlining特性を利用して、
googleで検索できます.
v 8関数のインラインはより多くの資料を調べます.
インライン化は
これに対応して、
ここの
まだないです.ソースのメモを読みます.
下のは私の公衆番号の二次元コード写真です.ご注意ください.
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で定義されています.ソースを見てもすぐに発見できます.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.call
とcall
の方法の違いは、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.まとめまだないです.ソースのメモを読みます.
下のは私の公衆番号の二次元コード写真です.ご注意ください.