天天看點

Promises與Javascript異步程式設計

Promises與Javascript異步程式設計

轉載:http://www.zawaliang.com/2013/08/399.html

在如今都追求使用者體驗的時代,Ajax應用真的是無所不在。加上這些年浏覽器技術、HTML5以及CSS3等的發展,越來越多的富Web應用出現;在給與我們良好體驗的同時,Web開發人員在背後需要處理越來越多的異步回調邏輯。

筆者對最近讀完的《Async Javascript-Build More Responsive Apps with Less Code》(Javascript異步程式設計-設計快速響應的網絡應用)一書以及部分資料,整理了我認為比較重要的一些點以及容易了解錯的地方,使大家對 Promise對象以及異步程式設計有更深的認識。

嵌套式回調

setTimeout(function() {
    setTimeout(function() {
    // do something
    }, 10)
}, 100);

$.ajax(url, function() {
        $.ajax(url2, function() {
                $.ajax(url3, function() {
            // do something
        });
    });
});
           

可是問題來了,當我們的嵌套越多,代碼結構層級會變得越來越深。首先是閱讀上會變得困難;其次是強耦合,接口變得不好擴充。我們需要一種模式來解決這種問題,這就是Promises所要做的事情。

異步函數類型

Javascript裡異步函數可以分為兩大類型:

  • I/O函數(Ajax、script…)
  • 計時函數(setTimeout、setInterval、setImmediate)

異步函數異常捕獲

try {
    setTimeout(function A() {
        setTimeout(function B() {
            setTimeout(function C() {
                throw new Error('Error');
            }, 0);
        }, 0);
    }, 0);
} catch (e) {}
           

運作以上代碼,A、B、C被添加到事件隊列裡;異常觸發時,A、B已被移出事件隊列,記憶體堆棧裡隻存在C,此時的異常不被try捕獲,隻會流向應用程式未捕獲異常處理器。

是以,在異步函數裡,不能使用try/catch捕獲異常。

分布式事件

Javascript的事件核心是事件分發機制,通過對釋出者綁定訂閱句柄來達到異步相響應的目的:

document.onclick = function() {
    // click
};
           

PubSub(Publish/Subscribe, 釋出/訂閱)模式,就是這麼一種模式,通過訂閱釋出者的事件響應來達到多層分發解耦的目的。

以下是一個簡單版本的PubSub模式實作:

var PubSub = (function() {
    var _handlers = {};
    return {
        // 訂閱事件
        on: function(eventType, handler) {
            if (!_handlers[eventType]) {
                _handlers[eventType] = [];
            }
        if (typeof handler == 'function') {
            _handlers[eventType].push(handler);
        }
        },
        //釋出事件
        emit: function(eventType) {
            var args = Array.prototype.slice.call(arguments, 1);
            var handlers = _handlers[eventType] || [];
            for (var i = 0, len = handlers.length; i < len; i++) {
                handlers[i].apply(null, args)
            }
        }
    };
})();
           

Promises/A規範

CommonJS之Promises/A規範是Kris Zyp于2009年提出來的,它通過規範API接口來簡化異步程式設計,使我們的異步邏輯代碼更易了解。

遵循Promises/A規範的實作我們稱之為Promise對象,Promise對象有且僅有三種狀态:unfulfilled(未完成)、 fulfilled(已完成)、failed(失敗/拒絕);而且狀态變化隻能從unfulfilled到fulfilled,或者 unfulfilled到failed;

Promise對象需實作一個then接口,then(fulfilledHandler, errorHandler, progressHandler);then接口接收一個成功回調(fulfilledHandler)與一個失敗回調(errorHandler);progressHandler觸發回調是可選的,Promise對象沒有強制去回調此句柄。

then方法的實作需要傳回一個新的Promise對象,以形成鍊式調用,或者叫Promise管道。

為了實作狀态的轉變,我們還需要實作另外兩個接口:

  • resolve:實作狀态由未完成到已完成
  • reject:實作狀态由未完成到拒絕(失敗)

這樣子我們開篇所說的嵌套式回調就可以這樣子寫了:

// 這裡假設Promise是一個已實作的Promise對象
function asyncFn1() {
    var p = new Promise();
    setTimeout(function() {
        console.log(1);
        p.resolve(); // 标記為已完成
    }, 2000);
    return p;
}
function asyncFn2() {
    var p = new Promise();
    setTimeout(function() {
        console.log(2);
        p.reject('error'); // 标記為拒絕
    }, 1000);
    return p;
}

asyncFn1()
    .then(function() {
        return asyncFn2();
    }).then(function() {
        console.log('done');
    }, function(err) {
        console.log(err);
    });
           

有了Promise,我們可以以同步的思維去編寫異步的邏輯了。在同步函數的世界裡,有2個非常重要的概念:

有傳回值

可以抛出異常

Promise不僅僅是一種可以鍊式調用的對象,更深層次裡,它為異步函數與同步函數提供了一種更加直接的對應關系。

上面我們說過,在異步函數裡,不能使用try/catch捕獲異常,是以也不能抛出異常。有了Promise,隻要我們顯式定義了errorHandler,那麼我們就可以做到像同步函數那樣的異常捕獲了。

繼續閱讀