JavaScript學習筆記-JS中的異步編程

剛開始學習JavaScript編程時,你可能就已經知道,JavaScript是單線程(Single Thread)執行的。單線程的意思是一次只能執行一個方法,只有等一個方法返回纔會去執行另一個方法。winform編程時如果UI線程中等待的話便會造成UI假死,但是在Web編程中沒有線程的概念,也就是說如果代碼等待則UI便會卡死。

爲了解決這個問題,Javascript語言將任務的執行模式分成兩種:同步(Synchronous)和異步(Asynchronous)。
同步模式便是上述所說的UI卡死的情況;
異步模式的話最普遍的例子就是Ajax方法。

本文總結一些JavaScript中異步編程的方法,如若文中有錯誤或者有其他異步方法,望不吝賜教。
在下文的示例中會使用setTimeout模擬耗時操作。

1、 回調方法(Callback)
回調是我剛開始接觸JavaScript異步時使用的方法,相信也是大多數人的體驗。

        var x = 0;

        function f1(callback) {
            setTimeout(function() {
                x += 1;
                callback();
            }, 100);
        }

        function f2() {
            alert(x);
        }

        f1(f2);

使用回調方法大致就是f1(f2)這樣的情況,但是當回調的層級過多的時候代碼便會呈現金字塔結構,使代碼變得難看並且不易理解和維護,重構的過程也會充滿各種陷阱。優點是這種方式會最原始最直接的,目前所有瀏覽器都能支持,所以也有很多人依然在用這種方式。

2、 事件監聽
爲了方便演示,下面示例使用JQuery的寫法。

    var x = 0;
    f1.on('done', f2);

    function f1(){
    setTimeout(function () {
      x += 1;
      f1.trigger('done');
    }, 1000);
  }

    function f2() {
        alert(x);
    }

    f1();

通過f1.on(‘done’, f2)添加事件監聽,當執行f1()方法,trigger觸發’done’事件,此時事件被捕捉並響應,執行f2方法。
使用事件監聽機制可以避免回調方法的多重嵌套,使代碼扁平化。但是使整個程序變成事件驅動模式,使流程更加不清晰(你有時很難看懂執行那個方法後觸發事件跳轉到另外的方法)。而且會破壞方法的原子性。

3、觀察者模式
觀察者模式與事件監聽機制類似,下面示例同樣使用JQuery的寫法。

    var x = 0;
    jQuery.subscribe('done', f2);

    function f1(){
    setTimeout(function () {
      x += 1;
      jQuery.publish('done');
    }, 1000);
  }

    function f2() {
        alert(x);
        jQuery.unsubscribe("done", f2);
    }

    f1();

可以看到,代碼上跟事件監聽的差異不大,差別只是
f1.on(‘done’, f2)變成了jQuery.subscribe(‘done’, f2),
f1.trigger(‘done’))變成了jQuery.publish(‘done’)。
實際上前者使用的是監聽,而後者更類似於通知。與前者相比的話優點是通過信號來統一控制。

4、Promise對象
ES6 原生提供了 Promise 對象。

所謂 Promise,就是一個對象,用來傳遞異步操作的消息。它代表了某個未來纔會知道結果的事件(通常是一個異步操作),並且這個事件提供統一的 API,可供進一步處理。

ES6的Promise來源於Promise/A+。使用Promise來進行異步流程控制,有幾個需要注意的問題,
We have a problem with promises一文中有很好的總結。
Promise 對象有以下兩個特點。

(1)對象的狀態不受外界影響。Promise 對象代表一個異步操作,有三種狀態:Pending(進行中)、Resolved(已完成,又稱 Fulfilled)和 Rejected(已失敗)。只有異步操作的結果,可以決定當前是哪一種狀態,任何其他操作都無法改變這個狀態。這也是 Promise 這個名字的由來,它的英語意思就是「承諾」,表示其他手段無法改變。

(2)一旦狀態改變,就不會再變,任何時候都可以得到這個結果。Promise 對象的狀態改變,只有兩種可能:從 Pending 變爲 Resolved 和從 Pending 變爲 Rejected。只要這兩種情況發生,狀態就凝固了,不會再變了,會一直保持這個結果。就算改變已經發生了,你再對 Promise 對象添加回調函數,也會立即得到這個結果。這與事件(Event)完全不同,事件的特點是,如果你錯過了它,再去監聽,是得不到結果的。

有了 Promise 對象,就可以將異步操作以同步操作的流程表達出來,避免了層層嵌套的回調函數。此外,Promise 對象提供統一的接口,使得控制異步操作更加容易。

Promise 也有一些缺點。首先,無法取消 Promise,一旦新建它就會立即執行,無法中途取消。其次,如果不設置回調函數,Promise 內部拋出的錯誤,不會反應到外部。第三,當處於 Pending 狀態時,無法得知目前進展到哪一個階段(剛剛開始還是即將完成)。

var promise = new Promise(function(resolve, reject) {
   if (/* 異步操作成功 */){
       resolve(value);
   } else {
       reject(error);
   }
});

promise.then(function(value) {
   // success
}, function(value) {
   // failure
});

Promise 構造函數接受一個函數作爲參數,該函數的兩個參數分別是 resolve 方法和 reject 方法。

如果異步操作成功,則用 resolve 方法將 Promise 對象的狀態,從「未完成」變爲「成功」(即從 pending 變爲 resolved);

如果異步操作失敗,則用 reject 方法將 Promise 對象的狀態,從「未完成」變爲「失敗」(即從 pending 變爲 rejected)。

更多關於Promise的特性,可以參考:阮一峯ECMAScript 6 入門

5、Generator方法
Generator 函數是協程在 ES6 的實現,最大特點就是可以交出函數的執行權(即暫停執行)。

    function* gen(x){
      var y = yield x + 2;
      return y;
    }

上面代碼就是一個 Generator 函數。它不同於普通函數,是可以暫停執行的,所以函數名之前要加星號,以示區別。

整個 Generator 函數就是一個封裝的異步任務,或者說是異步任務的容器。異步操作需要暫停的地方,都用 yield 語句註明。Generator 函數的執行方法如下。

   var g = gen(1);
   g.next() // { value: 3, done: false }
   g.next() // { value: undefined, done: true }

上面代碼中,調用 Generator 函數,會返回一個內部指針(即遍歷器 )g 。這是 Generator 函數不同於普通函數的另一個地方,即執行它不會返回結果,返回的是指針對象。調用指針 g 的 next 方法,會移動內部指針(即執行異步任務的第一段),指向第一個遇到的 yield 語句,上例是執行到 x + 2 爲止。

換言之,next 方法的作用是分階段執行 Generator 函數。每次調用 next 方法,會返回一個對象,表示當前階段的信息( value 屬性和 done 屬性)。value 屬性是 yield 語句後面表達式的值,表示當前階段的值;done 屬性是一個布爾值,表示 Generator 函數是否執行完畢,即是否還有下一個階段。

更多關於Generator的特性,可以參考:Generator 函數的含義與用法

5、async/await
async/await是ES7引進的新特性,即async function和await關鍵字,目前ES7由於瀏覽器支持等原因還未能投入生產環境,我們也只能暫時瞭解一下。

以下是async/await的一個簡單示例:

async function testFun() {
    let res, a, b, c, d;
    try {
        res = await f1(a, b);
        res = await f2(c, res);
        res = await f3(d);
        return res;
    } catch (err) {
        return handleError(err);
    }
}

testFun();

代碼跟Promise&Generator實現類似,此處不加以贅述。

以上就是目前JavaScript中實現異步的一些方法,有些直接複製他人的博文,參考的文章也均已給出,裏面有更加詳細的介紹。

發佈了24 篇原創文章 · 獲贊 11 · 訪問量 15萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章