再談Promise

以前寫了一篇關於ES6原生Promise的文章。近期又讀樸靈的《深刻淺出Node》,裏面介紹了一個Promise/Deferred模式。segmentfault

Promise是解決異步問題的利器。它實際上是一種模式。Promise有三種狀態,未完成態、完成態、失敗態,相信你們必定不陌生,Promise對象容許使用.then的形式,將回調放到IO操做等異步方法的主體以外,使代碼優美很多。數組

下面我結合《深刻淺出Node》,介紹一下如何用ES5實現Promise/Deferred模式。相信研究完該實現代碼以後,咱們會對Promise的理解更進一步。promise

Promise

then方法完成回調註冊app

Promise/Deferred模式下,Promise對象經過then方法調用,註冊完成態和失敗態的回調函數。異步

因爲then方法支持鏈式回調,所以then方法的返回值必定也是Promise對象,咱們在此簡單的返回自身,也就是thisasync

那麼必定有人要問了:then中的回調函數,可能返回一個新的Promise對象,此後的then調用是不是在新的Promise對象上調用的呢?函數

答案是:不必定this

這個問題其實困擾我好久,直到看了Promise的實現代碼我纔想明白。其實then方法的調用,只不過是註冊了完成態和失敗態下的回調函數而已。這些回調函數組成一個回調隊列,處理resolve的值。prototype

Promise構造函數註冊回調隊列code

Promise構造函數,給每一個實例一個queue屬性,將then方法註冊的回調隊列,保存在Promise實例的回調隊列中。

代碼

var Promise = function(){
    this.queue = [];
} 

Promise.prototype.then = function(fulfilledHandler, unfulfilledHandler){
    var handler = {};
    if (typeof fulfilledHandler === "function"){
        handler.fulfilled = fulfilledHandler;
    }
    if (typeof unfulfilledHandler === "function"){
        handler.unfulfilled = unfulfilledHandler;
    }
    this.queue.push(handler);
    return this;
}

咱們看到,Promise的代碼很簡單,只是經過then方法將一系列的回調函數push到隊列中而已。Promise實例暴露給用戶的也只有一個then方法。

這樣咱們就能夠這樣調用了:

promise.then(fulfilledFunc1, unfulfilledFunc1)
    .then(fulfilledFunc2, unfulfilledFunc2)
    .then(fulfilledFunc3, unfulfilledFunc3)

那麼如何進行狀態轉換呢?下面我就來說一下帶有resolve方法(reject方法同理,下面均以resolve舉例)的Deferred。

Deferred

Deferred實例決定Promise實例的狀態

每一個Deferred實例的對應一個Promise實例。調用Deferred實例的resolve方法,能使Promise註冊的回調隊列中的回調函數依次執行。

先寫部分代碼:

var Deferred = function(){
    this.promise = new Promise();
}

Deferred.protoype.resolve = function(val){
    var handler, value = val;
    while(handler = this.promise.queue.shift()){
        if (handler && handler.fulfilled){
            value = handler.fulfiller(value) && value;
        }
    }
}

這樣咱們就能使用Deferred實例返回Promise實例,而且使用Deferred實例的resolve方法來觸發Promise實例的完成態回調,而且將上一個回調若是有返回值,咱們將該返回值做爲新的resolve值傳遞給後面的回調。

處理回調方法返回的Promise實例

根據Promise模式,回調函數返回Promise實例時,下一個then()中的回調處理的是新的Promise實例。

在以前的代碼實現中,then方法註冊了一系列的回調函數,這些回調函數應該處理新的promise實例。這裏咱們用了一個小技巧,見代碼:

Deferred.protoype.resolve = function(val){
    var handler, value = val;
    while(handler = this.promise.queue.shift()){
        if (handler && handler.fulfilled){
            value = handler.fulfiller(value) && value;
            // 修改之處在這裏:
            if (value && value.isPromise){
                value.queue = this.promise.queue;
                // 最後再加一個小技巧
                this.promise = value;
                
                return;
            }
        }
    }
}

咱們將返回promise實例以後的回調列表原封不動的註冊到返回的promise中,這樣就保證以前then註冊的回調隊列能繼續調用。最後的小技巧可使舊的deferred實例對應新的promise實例,這樣能夠繼續使用deferred.resolve方法。

爲了判斷實例是不是Promise實例,這裏簡單的修改Promise構造函數:

var Promise = function(){
    this.queue = [];
    this.isPromise = true;
}

封裝callback方法用於異步調用

Promise之因此是解決異步的利器,一方面是then方法的鏈式調用,一方面也是由於resolve方法能夠異步調用,觸發回調隊列。

因爲以NodeJS爲標誌的異步方法其回調函數相似於這樣:

asyncFunction(param, function(err, data){
    // do something...
});

咱們能夠封裝一個本身的callback方法,用於異步觸發resolve方法。

Deferred.prototype.callback = function(err, data){
    if (err){
        this.reject(err);
    }
    this.resolve(data);
}

此後咱們能夠這樣promisify一個異步函數:

var async = function(param){
    var defer = new Deferred();
    var args = Array.prototype.silce.call(arguments);
    args.push(defer.callback);
    asyncFunc.apply(null, args);
    return defer.promise;
}

Promisify

由上面的promisify思路,咱們寫一個更通常化的promisify函數:

var promisify = function(method){
    return function(){
        var defer = new Deferred();
        var args = Array.prototype.silce.call(arguments, 1);
        args.push(defer.callback);
        asyncFunc.apply(null, args);
        return defer.promise;
    }
}

舉一個Node中文件操做的例子:

readFile = promisify(fs.readFile);

readFile('file1.txt', 'utf8').then(function(file1){
    return readFile(file1.trim(), 'utf8');
}).then(function(file2){
    console.log(file2);
})

倆字:優雅。

結束

相關文章
相關標籤/搜索