JavaScript 異步編程開源庫

1.jQuery.Defered

1.1 什么是 deferred 對象

開發(fā)網(wǎng)站過程中,我們經(jīng)常遇到某些耗時很長的 JavaScript 操作。
其中,既有異步的操作(比如 ajax 讀取服務(wù)器數(shù)據(jù)),也有同步的操作(比如遍歷一個大型數(shù)組)腾供,它們都不是立即能得到結(jié)果的。
通常的做法是鲜滩,為它們指定回調(diào)函數(shù)伴鳖,即事先規(guī)定,一旦它們運(yùn)行結(jié)束徙硅,應(yīng)該調(diào)用哪些函數(shù)榜聂。

簡單說,defered 對象就是 jQuery 的回調(diào)函數(shù)解決方法闷游,它是 jQuery 1.5.0 版本開始引入的新功能峻汉。

1.2 ajax 的鏈?zhǔn)綄懛?/h6>

jQuery 1.5以下版本的 ajax 操作的傳統(tǒng)寫法如下:

$.ajax({
  url:"test.html",
  success:function(){
    alert("成功了!");
  },
  error:function(){
    alert("失敗了脐往!");
  }
});

上面的代碼中休吠,$.ajax() 接受一個參數(shù)對象,這個對象包含兩個方法:success 方法指定操作成功后的回調(diào)函數(shù)业簿,error 方法指定操作失敗后的回調(diào)函數(shù)瘤礁。

jQuery 1.5以上版本,新的寫法如下:

$.ajax("test.html")
.done(function(){ alert("成功了梅尤!"); })
.fail(function(){ alert("失敗了柜思!");});

可以看到,done() 方法相當(dāng)于 success 方法巷燥,fail() 相當(dāng)于 error 方法赡盘。

1.3 指定同一操作的多個回調(diào)函數(shù)

deferred 對象的一大好處,就是它允許自由添加多個回調(diào)函數(shù)缰揪。直接把新的回調(diào)函數(shù)添加到后面就行了:

$.ajax("test.html")
.done(function(){ alert("成功了陨享!"); })
.fail(function(){ alert("失敗了!");})
.done(function(){ alert("這里是第二個回調(diào)函數(shù)!"); });

回調(diào)函數(shù)可以任意多個抛姑,它們按照添加順序執(zhí)行赞厕。

1.4 為多個操作指定回調(diào)函數(shù)

deferred 對象的另一好處,就是它允許你為多個事件指定一個回調(diào)函數(shù)定硝,這是傳統(tǒng)寫法做不到的皿桑。
它用到了一個新的方法 $.when():

$.when($.ajax("test1.html"),$.ajax("test.html"))
 .done(function(){ alert("成功了!"); })
 .fail(function(){ alert("失敗了蔬啡!"); });

這段代碼的意思是诲侮,先執(zhí)行兩個操作 $.ajax("test1.html") 和 $.ajax("test.html"),如果都成功了星爪,就運(yùn)行 done() 指定的回調(diào)函數(shù)浆西;如果有一個失敗了粉私,就執(zhí)行 fail() 回調(diào)函數(shù)顽腾。

1.5 普通操作的回調(diào)函數(shù)接口(上)

deferred 對象的最大優(yōu)點,就是它把這一套回調(diào)函數(shù)接口诺核,從 ajax 操作擴(kuò)展到了所有操作抄肖。也就是說,任何一個操作 —— 不管它是 ajax 還是本地操作窖杀,也不管是異步操作還是同步操作 —— 都可以使用 deferred 對象的各個方法漓摩,指定回調(diào)函數(shù)。
我們來看一個具體的例子入客。假定有一個很耗時的操作 wait:

var wait = function(){
  var tasks = function(){
    alert("執(zhí)行完畢管毙!");
  };
  setTimeout(tasks,5000);
}

很自然的,你會想到桌硫,可以使用 $.when():

$.when(wait())
 .done(function(){ alert("成功了夭咬!"); })
 .fail(function(){ alert("失敗了!"); });

但是铆隘,這樣寫的話卓舵,done() 方法會立即執(zhí)行,起不到回調(diào)函數(shù)的作用膀钠。原因在于 $.when() 的參數(shù)只能是 deferred 對象掏湾,所以必須對 wait() 進(jìn)行改寫:

var dtd = $.Deferred(); // 新建一個 deferred 對象
var wait = function(){
  var tasks = function(){
    alert("執(zhí)行完畢!");
    dtd.resolve(); // 改變 deferred 對象的執(zhí)行狀態(tài)
  }
  setTimeout(tasks,5000);
  return dtd;
}

現(xiàn)在肿嘲,wait() 返回的是 deferred 對象融击,這就可以加上鏈?zhǔn)讲僮髁恕ait() 函數(shù)運(yùn)行完雳窟,就會自動運(yùn)行 done() 方式指定的回調(diào)函數(shù)尊浪。

1.6 deferred.resolve() 方法和 deferred.reject() 方法

jQuery 規(guī)定,deferred 對象有三種執(zhí)行狀態(tài) —— 未完成,已完成和已失敗际长。如果執(zhí)行狀態(tài)是“已完成”(resolved)耸采,deferred 對象立即調(diào)用 done() 方法指定的回調(diào)函數(shù);如果執(zhí)行狀態(tài)是“已失敗”工育,調(diào)用 fail() 方法指定的回調(diào)函數(shù)虾宇;如果執(zhí)行狀態(tài)是“未完成”,則繼續(xù)等待如绸,或者調(diào)用 progress() 方法指定的回調(diào)函數(shù)(jQuery 1.7版本添加)嘱朽。

前面部分的 ajax 操作時,deferred 對象會根據(jù)返回結(jié)果怔接,自動改變自身的執(zhí)行結(jié)果但是搪泳,在 wait() 函數(shù)中,這個執(zhí)行狀態(tài)必須由程序員手動指定扼脐。 dtd.resolve() 的意思是岸军,將 dtd 對象的執(zhí)行狀態(tài)從“未完成”改為“已完成”,從而觸發(fā) done() 方法瓦侮。
類似的艰赞,還是存在一個 deferred.reject() 方法,作用是將 dtd 對象的執(zhí)行狀態(tài)從“未完成”改為“已失敗”肚吏,從而觸發(fā) fail() 方法方妖。

1.7 deferred.promise() 方法

上面這種寫法,還是有問題罚攀。那就是 dtd 是一個全局對象党觅,所以它的執(zhí)行狀態(tài)可以從外部改變。

var dtd = $.Deferred(); // 新建一個 deferred 對象
var wait = function(){
  var tasks = function(){
    alert("執(zhí)行完畢斋泄!");
    dtd.resolve(); // 改變 deferred 對象的執(zhí)行狀態(tài)
  }
  setTimeout(tasks,5000);
  return dtd;
}

$.when(wait())
 .done(function(){ alert("成功了杯瞻!"); })
 .fail(function(){ alert("失敗了!"); });

dtd.resolve();

上面的代碼尾部加了一行 dtd.resolve() 是己,這就改變了 dtd對象的執(zhí)行狀態(tài)又兵,因此導(dǎo)致 done() 方法立即執(zhí)行,跳出“成功了卒废!”的提示框沛厨,等5秒之后再跳出“執(zhí)行完畢!”的提示框摔认。
為了避免這種情況逆皮,jQuery 提供了 deferred.promise() 方法。它的作用是参袱,在原來的 deferred 對象上返回另一個 deferred 對象电谣,后者只開放與改變執(zhí)行狀態(tài)無關(guān)的方法(比如 done() 方法和 fail() 方法)秽梅,屏蔽與改變執(zhí)行狀態(tài)有關(guān)的方法(比如 resolve() 方法和 reject() 方法),從而使得執(zhí)行狀態(tài)不能改變剿牺。

請看下面代碼:

    var dtd = $.Deferred(); // 新建一個 deferred 對象
    var wait = function(){
        var tasks = function(){
            alert("執(zhí)行完畢企垦!");
            dtd.resolve(); // 改變 deferred 對象的執(zhí)行狀態(tài)
        }
        setTimeout(tasks,2000);
        return dtd.promise(); // 返回 promise 對象
    }

    var d = wait(); // 新建一個d對象,改為對這個對象進(jìn)行操作
    $.when(d)
   .done(function(){ alert("成功了晒来!"); })
   .fail(function(){ alert("失敗了钞诡!"); });

    d.resolve(); //此時,這個語句是無效的

在上面的這段代碼中湃崩,wait() 函數(shù)返回的是 promise 對象荧降。然后,我們把回調(diào)函數(shù)綁定在這個對象上面攒读,而不是原來的 deferred 對象上面朵诫。這樣的好處是,無法改變這個對象的執(zhí)行狀態(tài)薄扁,要想改變執(zhí)行狀態(tài)剪返,只能操作原來的 deferred 對象。

不過泌辫,更好的寫法是將 dtd 對象變成 wait() 函數(shù)的內(nèi)部對象随夸。

    var wait = function(){
        var dtd = $.Deferred(); // 新建一個 deferred 對象
        var tasks = function(){
            alert("執(zhí)行完畢!");
            dtd.resolve(); // 改變 deferred 對象的執(zhí)行狀態(tài)
        }
        setTimeout(tasks,2000);
        return dtd.promise(); // 返回 promise 對象
    }

    $.when(wait())
    .done(function(){ alert("成功了震放!"); })
    .fail(function(){ alert("失敗了!"); });
1.8 普通操作的回調(diào)函數(shù)接口(中)

另一種防止執(zhí)行狀態(tài)被外部改變的方法是驼修,使用 deferred 對象的構(gòu)建函數(shù) $.Deferred()殿遂。
這時,wait 函數(shù)還是保持不變乙各,我們直接將它傳入 $.Deferred:

$.Deferred(wait)
.done(function(){ alert("成功了!"); })
.fail( function(){ alert("失敗了墨礁!"); } );

jQuery 規(guī)定,$.Deferred() 可以接受一個函數(shù)名(注意耳峦,是函數(shù)名)作為參數(shù)恩静,$.Deferred() 所生成的 deferred 對象將作為這個函數(shù)的默認(rèn)參數(shù)。

1.9 普通操作的回調(diào)函數(shù)接口(下)

除了上面兩種方法以外蹲坷,我們還可以直接在 wait 對象上部署 deferred 接口驶乾。

    var dtd = $.Deferred(); // 新建一個 deferred 對象
    var wait = function(){
        var tasks = function(){
            alert("執(zhí)行完畢!");
            dtd.resolve(); // 改變 deferred 對象的執(zhí)行狀態(tài)
        }
        setTimeout(tasks,2000);
    }

    dtd.promise(wait);
    wait.done(function(){ alert("成功了循签!"); })
    .fail(function(){ alert("失敗了级乐!"); });
    wait(dtd);

這里的關(guān)鍵是 dtd.promise(wait); 這一行,它的作用就是在 wait 對象上部署 Deferred 接口县匠。正是因為有了這一行风科,后面才能直接在 wait 上面調(diào)用 done() 和 fail() 方法

1.10 小結(jié):defered 對象的方法

前面已經(jīng)講到了 deferred 對象的多種方法撒轮,下面做一個總結(jié):
(1) $.Deferred() 生成一個 deferred 對象。
(2) deferred.done() 指定操作成功時的回調(diào)函數(shù)贼穆。
(3) deferred.fail() 指定操作失敗時的回調(diào)函數(shù)题山。
(4) deferred.promise() 沒有參數(shù)時,返回一個新的 deferred 對象該對象的運(yùn)行狀態(tài)無法被改變故痊;接受參數(shù)時臀蛛,作用為參數(shù)對象上部署 deferred 接口。
(5) deferred.resolve() 手動改變 deferred 對象的運(yùn)行狀態(tài)為“已完成”崖蜜,從而觸發(fā) done() 方法浊仆。
(6) deferred.reject() 這個方法與 defered.resolve() 正好相反,調(diào)用后將 deferred 對象的運(yùn)行狀態(tài)變?yōu)椤耙咽 痹チ欤瑥亩|發(fā) fail() 方法抡柿。
(7) $.when() 為多個操作指定回調(diào)函數(shù)。
除了這些方法以外等恐,deferred 對象還有兩個重要方法:
(8) deferred.then()
有時為了省事洲劣,可以把 done() 和 fail() 合在一起寫,這就是 then() 方法:

$.when($.ajax("/main.php"))
.then(successFunc,failFunc)

如果 then() 有兩個參數(shù)课蔬,那么第一個是 done() 方法的回調(diào)函數(shù)囱稽,第二個參數(shù)是 fail() 方法的回調(diào)函數(shù)。如果 then() 只有一個函數(shù)二跋,那么等同于 done()战惊。
(9) deferred.always()
這個方法也是用來指定回調(diào)函數(shù)的,它的作用是扎即,不管調(diào)用的是 deferred.resolve() 還是 deferred.reject()吞获,最后總是執(zhí)行。

$.ajax("test.html")
.always(function(){ alert("已執(zhí)行谚鄙!"); });

來源:jQuery的deferred對象詳解

<br />

2.Q.js

Q.js 是知名各拷、功能完整的 Promise 函式庫,已經(jīng)實現(xiàn)了 Promise/A 標(biāo)準(zhǔn)闷营。
特點:

標(biāo)準(zhǔn)的回調(diào)函數(shù)的方式如下,嵌套比較深:

step1(function (value1) {
    step2(value1, function(value2) {
        step3(value2, function(value3) {
            step4(value3, function(value4) {
                // Do something with value4
            });
        });
    });
});

使用Q.js 后的代碼如下傻盟,采用依次排列:

Q.fcall(promisedStep1)
.then(promisedStep2)
.then(promisedStep3)
.then(promisedStep4)
.then(function (value4) {
    // Do something with value4
})
.catch(function (error) {
    // Handle any error from all above steps
})
.done();

來個簡單示例如下:

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
    <title>Q.js</title>
    <script src="../js/q-1.4.1.min.js"></script>
</head>
<body>
    <script>
        function qtest(num) {
            return Q.delay(num, 1000);
        }
        Q.all([
             qtest(10),
             qtest(20),
             qtest(30)
        ]).spread(function (x, y, z) {
            return x + y + z;
        }).done(function (str) {
            console.log("The sum is " + str + ".")
        });
    </script>
</body>
</html>

輸出結(jié)果為:

The sum is 60.

API 文檔地址:https://github.com/kriskowal/q/wiki/API-Reference

<br />

3.Koajs

Koajs 是基于 Node.js 平臺的下一代 Web 開發(fā)框架速蕊。使用 koa 編寫 web 應(yīng)用,通過組合不同的 generator莫杈,可以免除重復(fù)繁瑣的回調(diào)函數(shù)嵌套互例,并極大地提升常用錯誤處理效率。Koa 不在內(nèi)核方法中綁定任何中間件筝闹,它僅僅提供了一個輕量優(yōu)雅的函數(shù)庫媳叨,使得編寫 Web 應(yīng)用變得得心應(yīng)手腥光。

特點:
  • 基于 Generator,消滅回調(diào)代碼
  • 強(qiáng)大的異常處理
  • 實現(xiàn)了基礎(chǔ)的 http 工程糊秆,其他通過 middleware 實現(xiàn)武福、靈活
  • 官網(wǎng)地址:http://koajs.com/
  • 中文版地址:http://koa.bootcss.com/
示例:
<script>
    function * greet(name){
        yield "hello " + name + "!";
        yield "I hope you are enjoying the generator course";
        if(name.startsWith('Q')){
            yield "it's cool how your name starts width Q, " + name;
        }
        yield "see you later!";
    }
</script>

在谷歌瀏覽器中執(zhí)行結(jié)果:


中間件(Middle)

實際上,koa有很多第三方開發(fā)的中間件痘番,這些中間件的熟練運(yùn)用才是關(guān)鍵捉片,github 有很多這方面的庫。以下是常用的一些:

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末汞舱,一起剝皮案震驚了整個濱河市伍纫,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌昂芜,老刑警劉巖莹规,帶你破解...
    沈念sama閱讀 211,743評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異泌神,居然都是意外死亡良漱,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,296評論 3 385
  • 文/潘曉璐 我一進(jìn)店門欢际,熙熙樓的掌柜王于貴愁眉苦臉地迎上來母市,“玉大人,你說我怎么就攤上這事损趋』季茫” “怎么了?”我有些...
    開封第一講書人閱讀 157,285評論 0 348
  • 文/不壞的土叔 我叫張陵舶沿,是天一觀的道長墙杯。 經(jīng)常有香客問我,道長括荡,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,485評論 1 283
  • 正文 為了忘掉前任溉旋,我火速辦了婚禮畸冲,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘观腊。我一直安慰自己邑闲,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 65,581評論 6 386
  • 文/花漫 我一把揭開白布梧油。 她就那樣靜靜地躺著苫耸,像睡著了一般。 火紅的嫁衣襯著肌膚如雪儡陨。 梳的紋絲不亂的頭發(fā)上褪子,一...
    開封第一講書人閱讀 49,821評論 1 290
  • 那天量淌,我揣著相機(jī)與錄音,去河邊找鬼嫌褪。 笑死呀枢,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的笼痛。 我是一名探鬼主播裙秋,決...
    沈念sama閱讀 38,960評論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼缨伊!你這毒婦竟也來了摘刑?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,719評論 0 266
  • 序言:老撾萬榮一對情侶失蹤刻坊,失蹤者是張志新(化名)和其女友劉穎枷恕,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體紧唱,經(jīng)...
    沈念sama閱讀 44,186評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡活尊,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,516評論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了漏益。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片蛹锰。...
    茶點故事閱讀 38,650評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖绰疤,靈堂內(nèi)的尸體忽然破棺而出铜犬,到底是詐尸還是另有隱情,我是刑警寧澤轻庆,帶...
    沈念sama閱讀 34,329評論 4 330
  • 正文 年R本政府宣布癣猾,位于F島的核電站,受9級特大地震影響余爆,放射性物質(zhì)發(fā)生泄漏纷宇。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,936評論 3 313
  • 文/蒙蒙 一蛾方、第九天 我趴在偏房一處隱蔽的房頂上張望像捶。 院中可真熱鬧,春花似錦桩砰、人聲如沸拓春。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,757評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽硼莽。三九已至,卻和暖如春煮纵,著一層夾襖步出監(jiān)牢的瞬間懂鸵,已是汗流浹背偏螺。 一陣腳步聲響...
    開封第一講書人閱讀 31,991評論 1 266
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留矾瑰,地道東北人砖茸。 一個月前我還...
    沈念sama閱讀 46,370評論 2 360
  • 正文 我出身青樓,卻偏偏與公主長得像殴穴,于是被迫代替她去往敵國和親凉夯。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,527評論 2 349

推薦閱讀更多精彩內(nèi)容

  • jQuery的deferred對象詳解 作者:阮一峰 一采幌、什么是deferred對象劲够? 開發(fā)網(wǎng)站的過程中,我們經(jīng)常...
    JamHsiao_aaa4閱讀 318評論 0 0
  • Promise 的含義 一句話概括一下promise的作用:可以將異步操作以同步操作的流程表達(dá)出來休傍,避免了層層嵌套...
    雪萌萌萌閱讀 5,459評論 0 7
  • 一征绎、什么是deferred對象? 開發(fā)網(wǎng)站的過程中磨取,我們經(jīng)常遇到某些耗時很長的javascript操作人柿。其中,既有...
    壯哉我大前端閱讀 258評論 0 1
  • 1忙厌、什么是deferred對象 開發(fā)網(wǎng)站的過程中凫岖,我們經(jīng)常遇到某些耗時很長的javascript操作。其中逢净,既有異...
    公子七閱讀 262評論 1 3
  • 跟幾位老師聽課回來哥放。路上說起修行跟插座一樣,時而通時而斷爹土,一天中大部分時間都為煩惱所轉(zhuǎn)甥雕,想不起修行。問我怎么辦胀茵。 ...
    索訶閱讀 504評論 0 0