Callback與Promise間的橋梁-------promisify
Promise 自問世以來,得到了大量的應用,簡直是 javascript 中的神器。它很好地解決了異步方法的回調地獄、提供了我們在異步方法中使用 return 的能力,并將 callback 的調用納入了自己的管理,而不是交給異步函數后我們就無能為力了(經常有 callback 被莫名調用兩次而導致程序出錯)。
1.promisify 介紹
什么是 promisify 呢?顧名思義,就是“promise 化”,將一個不是promise的方法變成 promise 。舉個例子:
fs.readFile('test.js', function(err, data) {
if (!err) {
console.log(data);
} else {
console.log(err);
}
});
// promisify后
var readFileAsync = promisify(fs.readFile);
readFileAsync('test.js').then(data => {
console.log(data);
}, err => {
console.log(err);
});
這兩個方法效果上是等價的,但是從掌控性來說的話,我更喜歡后面的寫法。
那么什么樣的方法可以通過 promisify 變成 promise 呢?這里就需要介紹一個名詞,nodeCallback。什么樣的 callback 叫 nodeCallback ?
nodeCallback 有兩個條件:1. 回調函數在主函數中的參數位置必須是最后一個;2. 回調函數參數中的第一個參數必須是 error 。舉個例子:
1.回調函數在主函數中的參數位置
// 正確
function main(a, b, c, callback) {
}
// 錯誤
function main(callback, a, b, c) {
}
2. 回調函數參數中的第一個參數必須是 error
// 正確
function callback(error, result1, result2) {
}
// 錯誤
function callback(result1, result2, error) {
}
這樣,通過 nodeCallback ,我們定義了一個能被 promisify 的函數的格式,即,滿足 nodeCallback 形式的方法,我們可以通過 promisify 來讓它變成一個返回 promise 的方法。
2.promisify的實現
下面我們來根據上述條件來手動實現一個 promisify 。
首先 promisify 需要返回一個 function ,并且這個 function 要返回一個 promise
var promisify = (func) => {
return function() {
var ctx = this;
return new Promise(resolve => {
return func.call(ctx, ...arguments);
})
}
}
其次,原 func 的最后一個參數是 callback
var promisify = (func) => {
return function() {
var ctx = this;
return new Promise(resolve => {
return func.call(ctx, ...arguments, function() {
resolve(arguments);
});
})
}
}
然后,回調函數中的第一個參數是 error 標記
var promisify = (func) => {
return function() {
var ctx = this;
return new Promise((resolve, reject) => {
return func.call(ctx, ...arguments, function() {
var args = Array.prototype.map.call(arguments, item => item);
var err = args.shift();
if (err) {
reject(err);
} else {
resolve(args);
}
});
})
}
}
最后,做一些優(yōu)化,比如 this 作用域的自定義、回參只有一個時不返回數組
var promisify = (func, ctx) => {
// 返回一個新的function
return function() {
// 初始化this作用域
var ctx = ctx || this;
// 新方法返回的promise
return new Promise((resolve, reject) => {
// 調用原來的非promise方法func,綁定作用域,傳參,以及callback(callback為func的最后一個參數)
func.call(ctx, ...arguments, function() {
// 將回調函數中的的第一個參數error單獨取出
var args = Array.prototype.map.call(arguments, item => item);
var err = args.shift();
// 判斷是否有error
if (err) {
reject(err)
} else {
// 沒有error則將后續(xù)參數resolve出來
args = args.length > 1 ? args : args[0];
resolve(args);
}
});
})
};
};
測試
// nodeCallback方法func1
var func1 = function(a, b, c, callback) {
callback(null, a+b+c);
}
// promise化后的func2
var func2 = promisify(func1);
// 調用后輸出6
func1(1, 2, 3, (err, reuslt) => {
if (!err) {
console.log(result); //輸出6
}
})
func2(1, 2, 3).then(console.log); //輸出6
以上便是 promisify 的介紹和實現,事實上有很多用 callback 來實現異步的第三方庫提供的方法都是按照 nodeCallback 格式的,所以它們都可以通過 promisify 來讓它變成 promise ,在遇到這些方法的時候就可以更靈活地使用啦。