你所必須掌握的三種異步編程方法callbacks,listeners,promise

出處:你所必須掌握的三種異步編程方法callbacks,listeners,promisejavascript

前言

coder都知道,javascript語言運行環境是單線程的,這意味着任何兩行代碼都不能同時運行。多任務同時進行時,實質上造成了一個隊列,當隊列中前一個事件結束時,才執行下一個事件。 若是隊列中任何一個事務費時太長,則會形成瀏覽器假死,阻塞其餘事務正常進行,影響用戶體驗。html

js中將任務執行分爲同步模式和異步模式,上面一種即爲同步模式,任何比較花時間的代碼最好設計成異步模式。經過異步編程方式,便可以達到僞多進程。常見的異步模式主要有callbaks,listeners(一種觀察者模式),promise,下面我將會詳細描述三者各自的優劣。前端

Callbacks

這是再常見不過的異步編程方式了,任何前端都寫過這樣的代碼吧:html5

function callback(){
      console.log('do callbacks');
};
setTimeout(callback,300);

經過這種方式,你能夠簡單的實現異步編程。這裏須要注意,這裏的300ms只是理想時間,實際中各瀏覽器略有差別,主要取決於線程中其餘任務所用的時間。這個不具體討論,其中還涉及requestAnimationFrame等。本身能夠google。java

此方法缺點是什麼想必我不說你也知道,如:node

function fn1(f){
alert(1);
f();
};
function fn2(f){
alert(2);
f();
};
function fn3(){
alert(3);jquery

};git

fn1(function(){ fn2(fn3); });程序員

若是邏輯在複雜一點呢?恐怖看代碼的人會一頭霧水吧。而promise則能夠把這種複雜的嵌套寫的一看就明白,下面會講到。es6

Listeners

這也是一種常見的異步編程方式,如:

$elem.on('click',doClick);
function doClick(){
      //do something
};

設計模式中將這種方式稱之爲命令模式。這種方式的好處是你能夠註冊多個事件,待事件觸發時調用註冊的函數。

固然這裏不只僅是這種簡單的監聽方式,我須要將的是一種更抽象的異步編程方式——listeners。經過觀察者模式,咱們用一個數組來保存事件名稱以及事件綁定的函數方法,當須要觸發該事件時,咱們從數組中取出以前的註冊的函數。而EventProxy就是這種方式實現的,經過on,all註冊,emit觸發。如:

var ep = EventProxy.create("template", "data", "l10n", function (template, data, l10n) {
  _.template(template, data, l10n);
});

$.get("template", function (template) {
  // something
  ep.emit("template", template);
});
$.get("data", function (data) {
  // something
  ep.emit("data", data);
});
$.get("l10n", function (l10n) {
  // something
  ep.emit("l10n", l10n);
});

下面我用本身的代碼簡單的實現這種方式

var Observer = {
        _callbacks:{},
        register:function(name,fn,context){
            if(!name){
                return;
            }
            fn = fn || function(){};
            if(!Observer._callbacks[name]){
                var list = Observer._callbacks[name] = [];
            }
            list.push({fn:fn,context:context});
        },
        trigger:function(name){
            var lists = [];
            if(!name){ //觸發全部
                for(var key in Observer._callbacks){
                    lists.concat(Observer._callbacks[key]);
                }
            }else{ //觸發指定
                lists = Observer._callbacks[name];
            }
            for(var i = 0,len =lists.length;i<len;i++){
                var list = lists[i];
                var _fn = list.fn;
                return _fn.apply(list.context || this,[].slice.call(arguments,1));
            }
        }
    };
Observer.register('fetch',function(param){},this);
$.ajax(url,function(res){ Observer.trigger(name,res);  });

這種模式在於跨模塊異步編程中顯得尤其重要。具體的實現邏輯剛纔已經講過,就不具體展開,看代碼註釋便可。

Promise

最後一種方法隆重登場,也是如今討論最多的異步編程方式。

代碼實際上是現實的概括總結並以另一種方式展示,咱們有時候看不懂,是由於寫代碼方式跟現實有差別。假如代碼邏輯這麼寫:

img1.callThisIfLoadedOrWhenLoaded(function() {
  // 加載完成
}).orIfFailedCallThis(function() {
  // 加載失敗
});
// 以及……
whenAllTheseHaveLoaded([img1, img2]).callThis(function(){
    // 所有加載完成
}).orIfSomeFailedCallThis(function(){
    // 一個或多個加載失敗
});

我相信非程序員也能看懂吧。這就是咱們要介紹的Promise異步編程方法。現有一些插件庫Q.js,  when.js  ,wind.js  ,rsvp.js都是相似實現的,遵照一個通用的、標準化的規範:Promises/A+,jQuery 有個相似的方法叫 Deferred,但不兼容 Promises/A+ 規範,因而會有點小問題,使用需謹慎。jQuery 還有一個 Promise 類型,它實際上是 Deferred 的縮減版,因此也有一樣問題。jQuery相關的後續我會分章節逐一講述,這裏不作展開。

Promise異步編程總結起來就是實現了thenable,說的更直白的一點就是我跟你是好朋友,我可能有事須要你幫忙,你隨時候着吧,有須要我會第一時間通知你。你跟小王也是好哥們,並一樣告訴你須要他幫忙,並第一時間通知他。固然你通知他是在我通知你之後。用僞代碼表示:

myself(function(){
          callXiaoLi();
    })
    .then(function(){
         //我是小李,你喊我了,我就去喊小王
         callXiaowang();
    })
    .then(function(){ console.log('我是小王');  });

一樣,下面咱們會用簡單的代碼實現以上邏輯。

/**
 * author by 素人漁夫
 * email:dengxiaoming1217@gmail.com
 * description: 異步編程promise實現
 */
(function() {
    /*
     * promise
     */
    function EasyPromise() {
        this._callbacks = [];
    };
    EasyPromise.prototype = {
        then: function(func, context) {
            var p;
            if (this._isdone) {
                p = func.apply(context, this.result);
            } else {
                p = new EasyPromise();
                this._callbacks.push(function() {
                    //此時apply context很重要,不然,res中的實例獲取不到this
                    var res = func.apply(context, arguments);
                    if (res && typeof res.then === 'function') {
                        res.then(p.resolve, p);
                    }else{
                        p.resolve(res);
                    };
                });
            }
            return p;
        },
        resolve: function() {
            this.result = arguments;
            this._isdone = true;
            for (var i = 0; i < this._callbacks.length; i++) {
                this._callbacks[i].apply(this.result, arguments);
            }
            this._callbacks = [];
        }
    };
    EasyPromise.when = function() {
        var AllPromises = [].slice.call(arguments, 0)[0];
        var p = new EasyPromise();
        var promiseLen = AllPromises.length;

        var doneParams = {};
        AllPromises.forEach(function(item, index) {
            item.__promise__name = index;
            doneParams[index] = {};
            item.then(checkAllPromiseDone, item);
        });
        var donePromiseCount = 0;

        function checkAllPromiseDone(data) {
            donePromiseCount++;
            doneParams[this.__promise__name] = data;
            if (donePromiseCount == promiseLen) {
                doneParams = makeArray(doneParams);
                p.resolve(doneParams);
            }
        };
        return p;
    };


    if (typeof define !== 'undefined') {
        define([], function() {
            return EasyPromise;
        });
    } else if (typeof exports !== 'undefined') {
        exports.EasyPromise = EasyPromise;
    } else {
        window.EasyPromise = EasyPromise;
    }
})();

then實現比較繞,須要解釋一下,如:.then(xx).then()

每次.then時會生成一個新的promise對象, 就是說後一個then是註冊給上一個promise對象的,其出發條件依賴於上一個.then時生成的promise。

若是上一個.then 最後返回結果是非promise對象(即沒有then方法),則直接 resolve

若是上一個.then最後返回的結果是promise對象,那.then生成的promise必須依賴返回結果中的promise,及內層的 promise.then( ".then時生成的promise".resolve)

可能我這麼解釋仍是沒能解釋很清楚,本身去理解吧。

另外,谷歌瀏覽器已經實現了Promise API。我使用的式chrome 版本 31.0.1650.63。不行你能夠在控制檯輸入如下代碼測試:

var p = new Promise(function(resolve,reject){    
    setTimeout(resolve,100);
    setTimeout(reject,300); 
}); 
p.then(function(){ 
  alert('resolve'); 
},function(){ 
  alert('reject');
 });

好了,異步編程的方式就講這麼多,我這裏只不過是拋磚引玉,並無詳細闡述其api,而是以本身的代碼簡單實現其原來。這些異步編程方式不只僅限於瀏覽器環境,服務器環境如nodejs也可以使用。好比EventProxy原本就是在nodejs中發展而來的。

但願經過個人簡單講述能給你們提供幫忙。若是有錯,請幫忙指出,謝謝。

參考:

1.http://www.html5rocks.com/en/tutorials/es6/promises/

2.http://sporto.github.io/blog/2012/12/09/callbacks-listeners-promises/

3.http://www.ruanyifeng.com/blog/2012/12/asynchronous%EF%BC%BFjavascript.html

4.http://javascript.info/tutorial/events-and-timing-depth

相關文章
相關標籤/搜索