Javascript 異步編程的4種方法

因為 JavaScript 語言的執行環境是“單線程”(single thread)。

所謂“單線程”,就是指只能完成一件任務。如果有多個任務,就必須排隊,前面一個任務完成,再執行后面一個任務,以此類推。
這種模式的好處是實現起來比較簡單,執行環境比較單純;壞處是只要有一個任務耗時很長,后面的任務必須排隊等著,會拖延整個程序的執行。常見的瀏覽器無響應(假死),往往就是因為某一段 JavaScript 代碼長時間運行(比如死循環),導致整個頁面卡在這個地方,其他任務無法執行。

為了解決這個問題,JavaScript 語言將任務的執行模式分為兩種:同步(Synchronize)和異步(Asynchronous)。
“同步模式”就上一段的模式,后一個任務等待前一個任務結束,然后再執行,程序的執行順序與任務的排列順序是一致的、同步的;
“異步模式”則完全不同,每個任務有一個或多個回調函數(callback),前一個任務結束后,不是執行后一個任務,而是執行回調函數,后一個任務則是不等前一個任務結束就執行,所以程序的執行順序與任務的排列順序是不一致的、異步的。
“異步模式”非常重要。在瀏覽器端,耗時很長的操作都應該異步執行,避免瀏覽器失去響應,最好的例子就是 Ajax 操作。在服務器端,“異步模式”甚至是唯一的模式,因為執行環境時候單線程,如果允許同步執行所有 http 請求,服務器性能會急劇下降,很快就會失去響應。

下面總結了“異步模式”變成的4種方法:
<br />

1.回調函數

這是異步編程最基本的方法。
假定有兩個函數 f1 和 f2,后者等待前者的執行結果。

f1();
f2();

如果 f1 是個很耗時的任務,可以考慮改寫 f1, 把 f2 寫成 f1 的回調函數。

function f1(callback){
  setTimeout(function(){
    //f1 的任務代碼
    callback();
},1000);
}

執行代碼就變成下面這樣:

f1(f2);

采用這種方式,我們把同步操作變成了異步操作,f1不會堵塞程序運行,相當于先執行程序的主要邏輯,將耗時的操作推遲執行。
回調函數的有點是簡單、容易理解和部署,缺點是不利于代碼的閱讀和維護,各個部分之間高耦合(Coupling),流程會很混亂,而且每個任務只能指定一個回調函數。

示例1
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Callback</title>
</head>
<body>
<script>
    function f1(callback){
       setTimeout(function(){
           console.log("F1 is working now");
           callback();
    },1000);
}
    function f2(){
        console.log("F2 is working now.");
    }
    f1(f2);
</script>
</body>
</html>

效果圖如下:



<br />

2.事件監聽

另一種思路是采用事件驅動模式。任務的執行不取決于代碼的順序,而取決于某個事件是否發生。
還是以 f1 和 f2 為例。首先,為 f1綁定一個事件(這里采用的 jQuery 的寫法)。

f1.on('done',f2);

上面這行代碼的意思是,當 f1 發生 done 事件,就執行 f2。然后,對 f1 進行改寫:

function f1(){
  setTimeout(function(){
    //f1 的任務代碼
    f1.trigger('done');
  },1000);
}

f1.trigger('done') 表示,任務代碼執行完成后,立即觸發 done 事件,從而開始執行 f2。
這種方法的優點是比較容易理解,可以綁定多個事件,每個事件可以指定多個回調函數,而且可以“去耦合”(Decoupling),有利于實現模塊化。缺點是整個程序都變成事件驅動型,運行流程會變得很不清晰。

示例2
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Event</title>
    <script src="../js/jquery-1.11.3.min.js"></script>
</head>
<body>
<script>
    function f1(){
        setTimeout(function(){
            console.log("F1 is working now.");
            $("body").trigger('done');
        },1000);
    }
    function f2(){
        console.log("F2 is working now.");
    }
    $("body").on('done',f2);
    f1();
</script>
</body>
</html>

<br />

3.發布/訂閱

上面所說的“事件”,完全可以理解成“信號”。
我們假定,存在一個“信號中心”,某個執行完成,就向信號中心“發布”(publish)一個信號,其他任務可以向信號中心“訂閱”(subscribe)這個信號,從而知道什么時候自己可以開始執行。這就叫做“發布/訂閱模式”(publish-subscribe pattern),又稱“觀察者模式”(observer pattern)。
這種模式有多種實現,下面采用的是 Ben Alman 的 Tiny Pub/Sub,這是 jQuery 的一個插件。

首先,f2 向“信號中心” jQuery訂閱 “done”信號。

jQuery.subscribe("done",f2);

然后, f1 進行如下改寫:

function f1(){
  setTimeout(function(){
    //f1 的任務代碼
    jQuery.publish("done");
  },1000);
}

jQuery.publish("done") 的意思是,f1 執行完成后,向“信號中心” jQuery 發布“done”信號,從而引發 f2 的執行。
此外,f2 完成執行后,也可以取消訂閱(unsubscribe)。

jQuery.unsubscribe("done",f2);

這種方法的性質與“事件監聽”類似,但是明顯優于后者。因為我們可以通過查看“消息中心”,了解存在多少信號、每個信號有多少訂閱者,從而監控程序的運行。

示例3
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Publish-Subscribe Pattern</title>
    <script src="../js/jquery-1.11.3.min.js"></script>
    <script src="../js/ba-tiny-pubsub.min.js"></script>
</head>
<body>
<script>
    function f1(){
        setTimeout(function(){
            console.log("F1 is working now.");
            $.publish("done");
        },1000);
    }
    function f2(){
        console.log("F2 is working now.");
        //$.unsubscribe("done",f2);
    }
    $.subscribe("done",f2);
    f1();
</script>
</body>
</html>

<br />

4.Promises 對象

Promises 對象是 CommonJS 工作組提出的一種規范,目的是為異步編程提供統一接口。
簡單說,它的思想是,每一個異步任務返回一個 Promise 對象,該對象有一個 then 方法,允許指定回調函數。比如,f1 的回調函數f2 ,可以寫成:

f1().then(f2);

f1 要進行如下改寫(這里使用的是 jQuery 的實現):

function f1(){
  var dfd = $.Deferred();
  setTimeout(function(){
   //f1 的任務代碼
    dfd.resolve();
  },1000);
  return dfd.promise;
}

這樣寫的優點在于,回調函數變成了鏈式寫法,程序的流程可以看得很清楚,而且有一整套的配套方法,可以實現許多強大的功能。
比如,指定多個回調函數:

f1().then(f2).then(f3);

比如,指定發送錯誤時的回調函數:

f1().then(f2).fail(f3);

而且,它還有一個前三種方法都沒有的好處:如果一個任務已經完成,再添加回調函數,該回調函數會立即執行。所以,不用擔心是否錯過了某個事件或者信號。這種方法的缺點就是編寫和理解,都相對比較難。

示例4
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Promises Object</title>
    <script src="../js/jquery-1.11.3.min.js"></script>
</head>
<body>
<script>
    function f1(){
        var dfd = $.Deferred();
        setTimeout(function(){
            console.log("F1 is working now.");
            dfd.resolve();
        },1000);
        return dfd.promise();
    }
    function f2(){
        console.log("F2 is working now.");
    }
    f1().then(f2);
</script>
</body>
</html>

<br />

5.參考鏈接

<br />
來源:Javascript異步編程的4種方法

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,431評論 6 544
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,637評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,555評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,900評論 1 318
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,629評論 6 412
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,976評論 1 328
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,976評論 3 448
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,139評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,686評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,411評論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,641評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,129評論 5 364
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,820評論 3 350
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,233評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,567評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,362評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,604評論 2 380

推薦閱讀更多精彩內容

  • 你可能知道,Javascript語言的執行環境是"單線程"(single thread)。所謂"單線程",就是指一...
    乖乖果效36閱讀 151評論 0 0
  • Javascript語言的執行環境是"單線程"(single thread)。 所謂"單線程",就是指一次只能完成...
    小翼_b998閱讀 210評論 0 0
  • 你可能知道,Javascript語言的執行環境是"單線程"(single thread)。所謂"單線程",就是指一...
    wyude閱讀 290評論 0 2
  • 我親愛的笑笑小寶, 此時你在門內SBS,我在門外等你。新一天的陪伴開始了。 昨晚你自言自語地說:“上個星期SBS請...
    Graciegu閱讀 235評論 0 1
  • 文/青峰 雨在不停地下 一個流浪的乞丐 棲息在橋洞下 傾聽行人回家的腳步聲 雨水從眼中流下 靜靜咀嚼雨滴下的寂靜 ...
    羅布泊的小哆啦閱讀 123評論 0 0