参考文档Promise.promisify升级bluebird后的函数回调参数问题3:3中的使用方法还是和2不一样HowdoesBluebirdpromisifywork?:源码讲解promiify的内部机制;OptimizingforV8-Inlining,Deoptimizations:V8优化相关内容文章Promise.promisify:API官方文档1.简介用过Bluebird的人都知道promisify方法的作用。通过这个方法,将NodeJS形式的函数样式转换成一个Promise方法,可以认为是一个语法糖,例如:varreadFile=Promise.promisify(require("fs").readFile);readFile("myfile.js","utf8").then(function(contents){returneval(contents);}).then(function(result){//othercode})接下来我们分析一下内部流程这个承诺。下面,我们将以如下的代码片段作为demo来讲解varPromise=require('bluebird');varfs=require('fs');//这就是你读取文件的方式没有promisifyfs.readFile('/etc/profile',function(err,buffer){console.log('fs.readFile:'+buffer.toString());});//这是promisified版本varpromisifiedRead=Promise.promisify(fs.readFile);promisifiedRead('/etc/profile').then(function(buffer){console.log('promisifiedreadFile:'+buffer.toString());});2.在文件promisify.js中开始剖析:varmakeNodePromisified=canEvaluate?makeNodePromisifiedEval:makeNodePromisifiedClosure;....functionpromisify(callback,receiver,multiArgs){返回makeNodePromisified(callback,receiver,undefined,callback,null,multiArgs);}Promise.promisify=function(fn,options){if(typeoffn!=="function"){thrownewTypeError("期待一个函数但是得到了"+util.classString(fn));}if(isPromisified(fn)){r返回fn;}选项=对象(选项);varreceiver=options.context===未定义?这:options.context;varmultiArgs=!!options.multiArgs;varret=promisify(fn,receiver,multiArgs);.copyDescriptors(fn,ret,propsFilter);返回ret;};options最基本的形式是{context:this,multiArgs:false},本质是调用makeNodePromisifiedEval或makeNodePromisifiedClosure方法,根据canEvaluate变量选择,变量在文件./util.js中定义,而且看源码一句话就能很快找到。varcanEvaluate=typeofnavigator=="undefined";导航器包含有关访问者浏览器的信息。这里主要是区分是否是Node环境;在Promise.promisify官方API文档中提到,context是需要绑定的context对象:varredisGet=Promise.promisify(redisClient.get,{context:redisClient});redisGet('foo').then(function(){//...});也可以这样写:vargetAsync=Promise.promisify(redisClient.get);getAsync.call(redisClient,'foo').then(function(){//...});多参数可以查到在升级bluebird3后Promise.promisify的函数回调参数问题;如果canEvaluate为true,则表示处于Node环境,否则处于浏览器环境;首先我们看一下makeNodePromisifiedClosure2在浏览器端的实现。1、makeNodePromisifiedClosure对应的源码为:(为了方便阅读,也写下相关注释));var方法=回调;if(typeofmethod==="string"){回调=fn;}functionpromised(){var_receiver=receiver;如果(接收者===这个)_receiver=这个;varpromise=newPromise(内部);//_captureStackTrace方法添加了一个堆栈跟踪,以便于调试;承诺._captureStackTrace();//获取回调函数的定义:如果是方法名,则调用this[method],否则直接调用回调varcb=typeofmethod==="string"&&this!==defaultThis?这个[方法]:回调;varfn=nodebackForPromise(promise,multiArgs);尝试{cb.apply(_receiver,withAppended(arguments,fn));}catch(e){promise._rejectCallback(maybeWrapAsError(e),true,true);}if(!promise._isFateSealed())亲mise._setAsyncGuaranteed();回报承诺;}util.notEnumerableProp(promisified,"__isPromisified__",true);returnpromisified;}这里的nodebackForPromise方法相当于一个工厂函数,你可以把它想象成某种类型的promise生成器,这个名字中的nodeback这个词是不是把你搞糊涂了?,不过相信看完源码会让你恍然大悟,哈哈,我们先来看看它的源码(在./nodeback.js文件中)functionnodebackForPromise(promise,multiArgs){returnfunction(err,value){if(promise===null)返回;if(err){varwrapped=wrapAsOperationalError(maybeWrapAsError(err));promise._attachExtraTrace(包装);承诺._reject(包装);}elseif(!multiArgs){promise._fulfill(value);}else{INLINE_SLICE(args,arguments,1);承诺._fulfill(args);}承诺=空;};}这个方法返回一个函数function(err,value){....},仔细想想,这个风格是不是节点回调方法的风格?这不仅解释了nodebackForPromise名字的由来,也解释了promisify方法只对node异步函数(如fs.readFile等)有效;nodebackForPromise的逻辑比较简单,如果有错误调用promise._reject,如果成功则调用promise._fulfill。这也包括multiArgs参数的处理。如果返回多个参数,则将多个参数整合成数组形式;好了,我们回到主流程,代码执行到nodebackForPromise这一行,仍然没有对我们传入的回调方法进行特殊处理;直到cb.apply(_receiver,withAppended(arguments,fn));这里的withAppended方法定义在./util.js中,是一个纯函数,用于拼接数组,所以withAppended(arguments,fn)只是扩展了一个节点回调式的fn给已有的入参;在我们的demo中:varpromisifiedRead=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的操作过程类似;正因为它运行在节点端,所以可以使用V8引擎优化性能,利用其函数的内联特性,大大节省了调用回调方法时创建闭包的成本;你可以通过在谷歌上搜索v8函数内联来搜索更多信息;内联对callback.apply方法不起作用,除非它调用arguments参数,正如我们在上面看到的,我们对这个参数使用withAppended(arguments,fn),它返回一个新的参数数组,所以内联优化不起作用;相应地,callback.call方法可以优化内联;call和apply方法的区别在于apply接受一个数组作为参数,而call必须详细指定每个参数(也是如此,可用于内联优化);makeNodePromisifiedEval只是替换了上面的apply方法Call方法,为了达到V8引擎的最大优化性能——所以引擎必须知道输入参数的总数makeNodePromisifiedEval=function(callback,receiver,originalName,fn,_,multiArgs){varnewParameterCount=Math.max(0,parameterCount(fn)-1);varbody="'usestrict';\n\varret=function(Parameters){\n\'usestrict';\n\varlen=arguments.length;\n\varpromise=newPromise(INTERNAL);\n\promise._captureStackTrace();\n\varnodeback=nodebackForPromise(promise,"+multiArgs+");\n\varret;\n\varcallback=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\返回承诺;\n\};\n\notEnumerableProp(ret,'__isPromisified__',true);\n\返回ret;\n\".replace("[CodeForSwitchCase]",generateArgumentSwitchCase()).replace("Parameters",parameterDeclaration(newParameterCount));returnnewFunction("Promise","fn","receiver","withAppended",“可能是WrapAsError”,"nodebackForPromise","tryCatch","errorObj","notEnumerableProp","INTERNAL",body)(Promise,fn,receiver,withAppended,maybeWrapAsError,nodebackForPromise,util.tryCatch,util.errorObj,util.notEnumerableProp,内部);};为了根据不同的回调构造不同的内联方法,makeNodePromisifiedEval使用了原始函数构造函数。构造函数的参数从Promise开始,到INTERNAL结束;body变量是真正的函数体,可以发现大部分代码和makeNodePromisifiedClosure方法是一样的,唯一不同的是多了一段CodeForSwitchCase,用于生成不同的.call函数调用对于不同的输入参数;这里的generateArgumentSwitchCase函数比较复杂,这里就不展开了,总之最终会生成类似下面的代码:switch(len){case2:ret=callback.call(this,_arg0,_arg1,节点返回);休息;案例1:ret=callback.call(this,_arg0,nodeback);休息;案例0:ret=callback.call(this,nodeback);休息;案例3:ret=callback.call(this,_arg0,_arg1,_arg2,nodeback);休息;3。暂无总结,阅读源码备注下方是我的公众号二维码图,欢迎关注
