参考文档
- 晋级bluebird 3后Promise.promisify的函数回调参数题目:3中的运用要领和2照样不一样的
- How does Bluebird promisify work?:源码解说promiify的内部机制;
- Optimizing for V8 – Inlining, Deoptimizations:V8优化相干内容文章
- 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 的内部流程。下文,我们将以以下的代码片断作为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}
, - 实质是挪用
makeNodePromisifiedEval
或者是makeNodePromisifiedClosure
要领,根据 canEvaluate 变量挑选,该变量是在文件 ./util.js 中定义的,看源码也很快能发明就一句话var canEvaluate = typeof navigator == "undefined";
navigator 包括有关访问者浏览器的信息,这里主假如辨别是不是是Node环境;
在 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
的参数能够在 晋级bluebird 3后Promise.promisify的函数回调参数题目 中找到示例;
canEvaluate
为true示意在Node环境,不然在浏览器环境;起首我们看在浏览器端的完成 makeNodePromisifiedClosure
2.1、makeNodePromisifiedClosure
响应的源代码是:(轻易浏览也写上相干的诠释)
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;
}
这里的 nodebackForPromise
要领相当于工场函数,你能够设想成是 某种范例的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;
};
}
这个要领返回的是一个函数 function(err,value){….},细致想一想,这类作风是不是是 node回调要领的作风 ?这不只诠释了这也就诠释了 nodebackForPromise 名字的来源,也诠释了 promisify 要领只能对 node异步函数(比方fs.readFile
等)有用;
nodebackForPromise 个中的逻辑就比较简单了,假如有毛病就挪用promise._reject
,胜利就挪用promise._fulfill
,这里也包括了 multiArgs 参数的处置惩罚,假如返回多个参数,就把多个参数整合成数组情势;
好了,我们回到主流程,代码实行到 nodebackForPromise 这一行依然还没有对我们传入的 callback
要领做特别处置惩罚;
直到 cb.apply(_receiver, withAppended(arguments, fn));
这里的withAppended
要领定义在 ./util.js中,是一个纯函数,用于拼接数组的,因而withAppended(arguments, fn)
仅仅是给现有的入参扩大一个node回调作风的fn
;
在我们的 demo 里:
var promisifiedRead = Promise.promisify(fs.readFile);
promisifiedRead('/etc/profile')
实行到这里,实质上就是实行 fs.readFile.apply(this,'/etc/profile',fn)
,是不是是就很清楚了,实在和原有的挪用体式格局是一样的!仅仅是在 fn 中加入了promise功用;那末一旦 fs.readFile 实行完成,以后就会挪用 fn
要领,也就进入了promise的天下了; 棒棒哒!
2.2、makeNodePromisifiedEval
实在上述解读了 makeNodePromisifiedClosure
要领置信已了解了 promisify 这类魔法的实质,这节要讲的 makeNodePromisifiedEval
的操纵流程也是相似的;
只是由于运行在 node 端,能够 应用V8引擎优化机能,应用其 function inlining 特征,在挪用callback
要领时 极大地勤俭建立闭包的本钱;
可经由过程google搜刮
v8 函数内联 来查阅更多材料;
内联化对 callback.apply
要领是 不起作用的,除非它挪用的是 arguments 参数,而上面我们也看到了,这个参数我们运用 withAppended(arguments, fn)
,返回的是一个新的参数数组,因而内联优化是不起作用的;
与此相对应的,callback.call
要领能够被内联优化;call
和 apply
要领的区分在于,apply接收一个数组作为参数,而call 必需细致指定每个参数(也恰是如此,能够用于内联优化);makeNodePromisifiedEval
恰是将上述apply
要领替换成call
要领,以希冀到达V8引擎最大的优化机能 —— 因而必需让引擎晓得入参个数总数
makeNodePromisifiedEval =
function(callback, receiver, originalName, fn, _, multiArgs) {
var newParameterCount = Math.max(0, parameterCount(fn) - 1);
var body = "'use strict'; \n\
var ret = function (Parameters) { \n\
'use strict'; \n\
var len = arguments.length; \n\
var promise = new Promise(INTERNAL); \n\
promise._captureStackTrace(); \n\
var nodeback = nodebackForPromise(promise, " + multiArgs + "); \n\
var ret; \n\
var callback = tryCatch(fn); \n\
switch(len) { \n\
[CodeForSwitchCase] \n\
} \n\
if (ret === errorObj) { \n\
promise._rejectCallback(maybeWrapAsError(ret.e), true, true);\n\
} \n\
if (!promise._isFateSealed()) promise._setAsyncGuaranteed(); \n\
return promise; \n\
}; \n\
notEnumerableProp(ret, '__isPromisified__', true); \n\
return ret; \n\
".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);
};
为了能根据差别的callback组织差别的内联要领,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. 总结
暂无,浏览源码笔记
下面的是我的民众号二维码图片,迎接关注。