前端开发尤其 Node.js 开发中,经常要调用一些异步接口,如:文件操作、网络数据读取。而这些接口默认情况下往往是通过 Callback 方式提供的,即:最后一个参数传入一个回调函数,当出现异常时,将错误信息作为第一个参数传给回调函数,如果正常,第一个参数为 null,后面的参数为对应其他的值。
var fs = require("fs");
fs.readFile("foo.json", "utf8", function(err, content){
if(err){
//异常情况
}else{
//正常情况
}
})
当这种写法遇上比较复杂的逻辑时,就很容易出现 callback hell 的问题。为此,开发者也积极寻找对应的解决方案,如:Promise、ES6 Generator + co + Promise、ES2016 草案里的 async functions 等。
这几种方案也是慢慢的在进化,试图更好的处理 callback hell 的问题。但这几种方案一致的依赖基础方式都是 Promise,这也是为什么 Promise 并没有引入新的语法但也写进了 ES6 规范的一个大的原因。甚至现在一些新的接口(如:Fetch)直接返回 Promise。
然后对异步接口的处理方式都依赖 Promise,那么下面就来说下如何将 Callback 接口变成 Promise 接口。
其实 Callback 接口变成 Promise 接口非常简单,包括现在也有很多库都有类似的方法可以转换,如:
由于 Callback 接口的参数方式是固定的,所以很容易变成 Promise 接口,如:
let promisify = (fn, receiver) => {
return (...args) => {
return new Promise((resolve, reject) => {
fn.apply(receiver, [...args, (err, res) => {
return err ? reject(err) : resolve(res);
}]);
});
};
};
几行代码基本就搞定了对 Callback 接口对 Promise 的转换,当然上面的代码是用 ES6 代码写的。用 ES5 写的话可以类似下面这样:
var promisify = function promisify(fn, receiver) {
return function () {
for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) {
args[_key] = arguments[_key];
}
return new Promise(function (resolve, reject) {
fn.apply(receiver, [].concat(args, [function (err, res) {
return err ? reject(err) : resolve(res);
}]));
});
};
};
有了 promisify
这样一个函数,那么把 Callback 接口变成 Promise 接口就非常简单了,如:
var fs = require("fs");
var readFilePromise = promisify(fs.readFile, fs); //包装为 Promise 接口
readFilePromise("foo.json", "utf8").then(function(content){
//正常情况
}).catch(function(err){
//异常情况
})
有了快速转换的方法后,就不用去找模块对应的 Promise 版本的模块了。
有些设计不合理的接口可能会传递多个值给回调函数,如:
var fn = function(foo, callback){
if(success){
callback(null, content1, content2);
}else{
callback(err);
}
}
上面的代码在正常情况下会传递 2 个参数给回调函数,由于 Promise resolve 的时候只能传入一个值,所以这种接口变成 Promise 接口后是无法获取到 content2 数据的。
对于这种情况只能手工来包装了,同时顺便鄙视下设计这个接口的人。
有些人担心大量使用 Promise 会引起性能的下降,这个事情在当初 Node.js 设计接口时也争吵了很久,有时候易用性和性能本来就是有些互斥的。
其实可以使用高性能的 Promise 库来提高性能,如:bluebird。简单对比测试发现,blurbird 的性能是 V8 里内置的 Promise 3 倍左右(bluebird 的优化方式见 https://github.com/petkaantonov/bluebird/wiki/Optimization-killers )。
可以通过下面的方式替换调内置的 Promise:
global.Promise = require("bluebird");
如果项目里用了 Babel 编译 ES6 代码的话,可以用下面的方式替换:
//Babel 编译时会把 Promise 编译为 Babel 依赖的 Promise
require("babel-runtime/core-js/promise").default = require("bluebird");
global.Promise = require("bluebird");
Copyright© 2013-2020
All Rights Reserved 京ICP备2023019179号-8