jQuery中的Promise


前言

這篇文章是為了要理解在jQuery中的Promise機制而整理的。

jQuery在幾年前就有Deferred物件的概念,當時Promise並未成為真正的標準,而今時今日Promise已成為ES6的正式標準,相信未來jQuery中的Promise作法,將會改變為實作ES6中的標準。

Promise的概念並不容易理解,從這裡開始反而簡單些,不論實作的方式如何,它的目的始終是差不多的,從以下的內容可窺得一二。

jQuery 1.5

在2010年左右時發佈的jQuery 1.5版本即導入了"Deferred(遞延、延期的)"的概念,用於"chain(串連)"多個callbacks成為一個callback queues。以下是來自jQuery官方文件的一段解說:

The Deferred object, ... is a chainable utility object created by calling the jQuery.Deferred() method. It can register multiple callbacks into callback queues, invoke callback queues, and relay the success or failure state of any synchronous or asynchronous function.

"Deferred"物件的使用,將原本異步的callbacks,從此可以串連在一起,每個callback可以進行有順序的處理流。

以Ajax為例

在jQuery 1.5前,一個最典型的範例是使用$.ajax()呼叫的程式:

$.ajax({
  url: "/myServerScript",
  success: mySuccessFunction,
  error: myErrorFunction
});

$.ajax()會回傳jQuery XMLHttpRequest物件。

在1.5版本之後,這個回傳的物件實作了CommonJS Promises/A介面,改成回傳Deferred或Promise相容的物件,程式碼寫成下面這樣:

var promise = $.ajax({
  url: "/myServerScript"
});

promise.done(mySuccessFunction);
promise.fail(myErrorFunction);

或是更簡單的,用串連(chain)的方式:

$.ajax({
  url: "/myServerScript"
})
.done(mySuccessFunction);
.fail(myErrorFunction);

而且還可以把donefail合併到then方法中,程式碼又更簡單了:

Promises/A標準中只有then方法,donefail方法是jQuery自有的

var promise = $.ajax({
  url: "/myServerScript"
});

promise.then(mySuccessFunction, myErrorFunction);

好吧,所以success方法的callback放到done方法;而error方法的callback放到fail方法,就這樣而已?

介面改變之後

可以連續呼叫多個callback

連續呼叫後,這些callback可以依順序執行,這是很特別的地方,也是這種機制最重要的效益。

var promise = $.ajax({
  url: "/myServerScript"
});

promise.done(myStopAnimationFunction);
promise.done(myOtherAjaxFunction);
promise.done(myShowInfoFunction);
promise.fail(myErrorFunction);

使用chain(串連)語法:

var promise = $.ajax({
  url: "/myServerScript"
});

promise.done(myStopAnimationFunction)
.done(myOtherAjaxFunction)
.done(myShowInfoFunction)
.fail(myErrorFunction);

可以合併不同的兩個Ajax呼叫

當兩個Ajax同時都success時才會執行某個callback方法,這裡可以看到用了一個新的$.when()方法:

var promise1 = $.ajax("/myServerScript1");
var promise2 = $.ajax("/myServerScript2");

$.when(promise1, promise2).done(function(xhrObject1, xhrObject2) {
  // Handle both XHR objects
});

自定會回傳Promise物件的函式

現在了解用法後,如果要寫自己的函式能回傳Promise物件,那該如何寫?先說明一下:

Deferred物件包含了以下兩個重要的方法:

  • resolve
  • reject

然後,它有三種重要的方法(或事件)來連接callback:

  • done
  • fail
  • always :不論失敗或成功都一定會執行

前有有說過了,then方法是donefail的兩個合體,也可以說donefail是從then分出的簡化版本。

還有一個方法是用在最後回傳Promise物件的:

  • promise

下面簡單的程式碼,可以看出來,對應done方法,我們在自已的函式中要使用deferred.resolve(),也就是說當deferred.resolve()一被呼叫,這個回傳的Promise相當於得到success的狀態。

那對應fail方法呢(也就是error狀態),則是要使用deferred.reject()方法。


   $('#result').html('waiting...');

    var promise = wait();

    promise.done(result);

    function result() {
        $('#result').html('done');
    }

    function wait() {
        var deferred = $.Deferred();

        setTimeout(function() {
            deferred.resolve();
        }, 2000);

        return deferred.promise();
    }

resolve()reject()兩者之間同時只能被呼叫一個,只要其中一個被呼叫就會"固定住"(鎖住)Promise的狀態,你可以把上面的範例中的deferred.resolve()改為deferred.reject(),然後再加一個faildone之後。

resolve或reject方法類似,差異只是一個代表成功狀態,另一個是代表失敗狀態。在方法裡是可以傳遞回傳值給下一個要呼叫的callback,一個簡單的範例如下:

   $('#result').html('waiting...');

    var deferred = $.Deferred();

    deferred.promise().done(result);

    setTimeout(function() {
        deferred.resolve("i'm success");
    }, 2000);

    function result(value) {
        $('#result').html(value);
    }

jQuery官網的Ajax範例,這個範例雖沒看到resolvereject,但它是隱藏實作中:


$.ajax({
  method: "POST",
  url: "some.php",
  data: { name: "John", location: "Boston" }
})
  .done(function( msg ) {
    alert( "Data Saved: " + msg );
  });

所以如果是jQuery中所定義會回傳Promise的方法,預設下個callback的第1個參數值就是現在這個callback回傳數值。如果有很多串連的callback,可以用這種方式將回傳值一個接一個傳遞下去。

Deferred與Promise的差異

從前面的說明,最明顯的就是"Deferred物件是用來構造Promise物件用的",事實上這個Promise物件並不是我們所說的標準Promise物件,特性也有很不同的地方。

不過,我個人覺得會用比較重要,要說到差異,說實在的網路上的文章很多,其實也沒那麼重要。為什麼呢?因為很快的又要改變作法了,Deferred物件是jQuery自創的東西,但現在快要有真正的Promise標準了,所以我反而覺得學標準用法比較會跟得上未來的時代。

jQuery從1.5到1.8版本,都有加強這Deferred部份的特性和一些方法,但始終不是所謂的"純正Promises/A"標準用法。在2015年7月,jQuery官網對外宣傳即將在3.0版本中實作Promises/A標準。這有可能和6月時,ECMAScript 6(ECMAScript 2015)版本已經定案通過,其中就有Promise的標準部份。

參考資料