Callback 與 Promise 間的橋樑 —— promisify

iKcamp發表於2017-11-01

作者:晃晃 本文原創,轉載請註明作者及出處

Promise 自問世以來,得到了大量的應用,簡直是 javascript 中的神器。它很好地解決了非同步方法的回撥地獄、提供了我們在非同步方法中使用 return 的能力,並將 callback 的呼叫納入了自己的管理,而不是交給非同步函式後我們就無能為力了(經常有 callback 被莫名呼叫兩次而導致程式出錯)。

今天要介紹的是 Promisify,就是回撥函式與 Promise 間的橋樑。

1. promisify 介紹

什麼是 promisify 呢?顧名思義,就是“promise 化”,將一個不是promise的方法變成 promise 。舉個例子:

// 原有的callback呼叫
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) {
    
}
複製程式碼
  1. 回撥函式引數中的第一個引數必須是 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);
                }
            });
        })
    }
}
複製程式碼

最後,做一些優化,比如 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則將後續引數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 ,在遇到這些方法的時候就可以更靈活地使用啦。

iKcamp官網:www.ikcamp.com

訪問官網更快閱讀全部免費分享課程:《iKcamp出品|全網最新|微信小程式|基於最新版1.0開發者工具之初中級培訓教程分享》。 包含:文章、視訊、原始碼

Callback 與 Promise 間的橋樑 —— promisify

iKcamp原創新書《移動Web前端高效開發實戰》已在亞馬遜、京東、噹噹開售。

【11月11號】上海iKcamp最新活動

Callback 與 Promise 間的橋樑 —— promisify

報名地址:www.huodongxing.com/event/54099…

“天天練口語”小程式總榜排名第四、教育類排名第一的研發團隊,面對面溝通交流。


Callback 與 Promise 間的橋樑 —— promisify

2019年,iKcamp原創新書《Koa與Node.js開發實戰》已在京東、天貓、亞馬遜、噹噹開售啦!

相關文章