在线观看不卡亚洲电影_亚洲妓女99综合网_91青青青亚洲娱乐在线观看_日韩无码高清综合久久

鍍金池/ 教程/ HTML/ 實(shí)戰(zhàn) Promise
Promise 測(cè)試
實(shí)戰(zhàn) Promise
用語(yǔ)集
前言
什么是 Promise
API Reference
Advanced

實(shí)戰(zhàn) Promise

本章我們將會(huì)學(xué)習(xí) Promise 提供的各種方法以及如何進(jìn)行錯(cuò)誤處理。

2.1. Promise.resolve

一般情況下我們都會(huì)使用 new Promise() 來(lái)創(chuàng)建 promise 對(duì)象,但是除此之外我們也可以使用其他方法。

在這里,我們將會(huì)學(xué)習(xí)如何使用 Promise.resolvePromise.reject 這兩個(gè)方法。

2.1.1. new Promise 的快捷方式

靜態(tài)方法 Promise.resolve(value) 可以認(rèn)為是 new Promise() 方法的快捷方式。

比如 Promise.resolve(42); 可以認(rèn)為是以下代碼的語(yǔ)法糖。

    new Promise(function(resolve){
        resolve(42);
    });

在這段代碼中的 resolve(42); 會(huì)讓這個(gè)promise對(duì)象立即進(jìn)入確定(即 resolved)狀態(tài),并將 42 傳遞給后面 then 里所指定的 onFulfilled 函數(shù)。

方法 Promise.resolve(value); 的返回值也是一個(gè) promise 對(duì)象,所以我們可以像下面那樣接著對(duì)其返回值進(jìn)行 .then 調(diào)用。

    Promise.resolve(42).then(function(value){
    console.log(value);
    });

Promise.resolve 作為 new Promise() 的快捷方式,在進(jìn)行 promise 對(duì)象的初始化或者編寫測(cè)試代碼的時(shí)候都非常方便。

2.1.2. Thenable

Promise.resolve 方法另一個(gè)作用就是將 thenable 對(duì)象轉(zhuǎn)換為 promise 對(duì)象。

ES6 Promises 里提到了 Thenable 這個(gè)概念,簡(jiǎn)單來(lái)說(shuō)它就是一個(gè)非常類似 promise 的東西。

就像我們有時(shí)稱具有 .length 方法的非數(shù)組對(duì)象為 Array like 一樣,thenable 指的是一個(gè)具有 .then 方法的對(duì)象。

這種將 thenable 對(duì)象轉(zhuǎn)換為 promise 對(duì)象的機(jī)制要求 thenable 對(duì)象所擁有的 then 方法應(yīng)該和 Promise 所擁有的 then 方法具有同樣的功能和處理過(guò)程,在將 thenable 對(duì)象轉(zhuǎn)換為 promise 對(duì)象的時(shí)候,還會(huì)巧妙的利用 thenable 對(duì)象原來(lái)具有的 then 方法。

到底什么樣的對(duì)象能算是 thenable 的呢,最簡(jiǎn)單的例子就是 jQuery.ajax(),它的返回值就是 thenable 的。

因?yàn)?jQuery.ajax() 的返回值是 jqXHR Objec 對(duì)象,這個(gè)對(duì)象具有 .then 方法。

    $.ajax('/json/comment.json');// => 擁有 `.then` 方法的對(duì)象

這個(gè) thenable 的對(duì)象可以使用 Promise.resolve 來(lái)轉(zhuǎn)換為一個(gè)promise對(duì)象。

變成了 promise 對(duì)象的話,就能直接使用 then 或者 catch 等這些在 ES6 Promises 里定義的方法了。

將 thenable 對(duì)象轉(zhuǎn)換 promise 對(duì)象

    var promise = Promise.resolve($.ajax('/json/comment.json'));// => promise對(duì)象
    promise.then(function(value){
       console.log(value);
    });

WARNING
jQuery 和 thenable

jQuery.ajax() 的返回值是一個(gè)具有 .then 方法的 jqXHR Object 對(duì)象,這個(gè)對(duì)象繼承了來(lái)自 Deferred Object 的方法和屬性。

但是 Deferred Object 并沒(méi)有遵循 Promises/A+ES6 Promises 標(biāo)準(zhǔn),所以即使看上去這個(gè)對(duì)象轉(zhuǎn)換成了一個(gè) promise 對(duì)象,但是會(huì)出現(xiàn)缺失部分信息的問(wèn)題。

這個(gè)問(wèn)題的根源在于 jQuery 的 Deferred Objectthen 方法機(jī)制與 promise 不同。

所以我們應(yīng)該注意,即使一個(gè)對(duì)象具有 .then 方法,也不一定就能作為 ES6 Promises 對(duì)象使用。

Promise.resolve 只使用了共通的方法 then ,提供了在不同的類庫(kù)之間進(jìn)行 promise 對(duì)象互相轉(zhuǎn)換的功能。

這種轉(zhuǎn)換為 thenable 的功能在之前是通過(guò)使用 Promise.cast 來(lái)完成的,從它的名字我們也不難想象它的功能是什么。

除了在編寫使用 Promise 的類庫(kù)等軟件時(shí)需要對(duì) Thenable 有所了解之外,通常作為 end-user 使用的時(shí)候,我們可能不會(huì)用到此功能。

我們會(huì)在后面第4章的 Promise.resolve 和 Thenable 中進(jìn)行詳細(xì)的說(shuō)明,介紹一下結(jié)合使用了 Thenable 和 Promise.resolve 的具體例子。

簡(jiǎn)單總結(jié)一下 Promise.resolve 方法的話,可以認(rèn)為它的作用就是將傳遞給它的參數(shù)填充(Fulfilled)到 promise 對(duì)象后并返回這個(gè) promise 對(duì)象。

此外,Promise 的很多處理內(nèi)部也是使用了 Promise.resolve 算法將值轉(zhuǎn)換為 promise 對(duì)象后再進(jìn)行處理的。

2.2. Promise.reject

Promise.reject(error)是和 Promise.resolve(value) 類似的靜態(tài)方法,是 new Promise() 方法的快捷方式。

比如 Promise.reject(new Error("出錯(cuò)了")) 就是下面代碼的語(yǔ)法糖形式。

    new Promise(function(resolve,reject){
        reject(new Error("出錯(cuò)了"));
    });

這段代碼的功能是調(diào)用該 promise 對(duì)象通過(guò) then 指定的 onRejected 函數(shù),并將錯(cuò)誤(Error)對(duì)象傳遞給這個(gè) onRejected 函數(shù)。

    Promise.reject(new Error("BOOM!")).catch(function(error){
        console.error(error);
    });

它和 Promise.resolve(value) 的不同之處在于 promise 內(nèi)調(diào)用的函數(shù)是 reject 而不是 resolve,這在編寫測(cè)試代碼或者進(jìn)行 debug 時(shí),說(shuō)不定會(huì)用得上。

2.3. 專欄: Promise 只能進(jìn)行異步操作?

在使用 Promise.resolve(value) 等方法的時(shí)候,如果 promise 對(duì)象立刻就能進(jìn)入 resolve 狀態(tài)的話,那么你是不是覺(jué)得 .then 里面指定的方法就是同步調(diào)用的呢?

實(shí)際上,.then 中指定的方法調(diào)用是異步進(jìn)行的。

    var promise = new Promise(function (resolve){
        console.log("inner promise"); // 1
        resolve(42);
    });
    promise.then(function(value){
        console.log(value); // 3
    });
    console.log("outer promise"); // 2

執(zhí)行上面的代碼會(huì)輸出下面的 log,從這些 log 我們清楚地知道了上面代碼的執(zhí)行順序。

    inner promise // 1
    outer promise // 2
    42// 3

由于 JavaScript 代碼會(huì)按照文件的從上到下的順序執(zhí)行,所以最開始 <1> 會(huì)執(zhí)行,然后是 resolve(42); 被執(zhí)行。這時(shí)候 promise 對(duì)象的已經(jīng)變?yōu)榇_定狀態(tài),F(xiàn)ulFilled 被設(shè)置為了 42 。

下面的代碼 promise.then 注冊(cè)了 <3> 這個(gè)回調(diào)函數(shù),這是本專欄的焦點(diǎn)問(wèn)題。

由于 promise.then 執(zhí)行的時(shí)候 promise 對(duì)象已經(jīng)是確定狀態(tài),從程序上說(shuō)對(duì)回調(diào)函數(shù)進(jìn)行同步調(diào)用也是行得通的。

但是即使在調(diào)用 promise.then 注冊(cè)回調(diào)函數(shù)的時(shí)候 promise 對(duì)象已經(jīng)是確定的狀態(tài),Promise 也會(huì)以異步的方式調(diào)用該回調(diào)函數(shù),這是在 Promise 設(shè)計(jì)上的規(guī)定方針。

因此 <2> 會(huì)最先被調(diào)用,最后才會(huì)調(diào)用回調(diào)函數(shù) <3>

為什么要對(duì)明明可以以同步方式進(jìn)行調(diào)用的函數(shù),非要使用異步的調(diào)用方式呢?

2.3.1. 同步調(diào)用和異步調(diào)用同時(shí)存在導(dǎo)致的混亂

其實(shí)在 Promise 之外也存在這個(gè)問(wèn)題,這里我們以一般的使用情況來(lái)考慮此問(wèn)題。

這個(gè)問(wèn)題的本質(zhì)是接收回調(diào)函數(shù)的函數(shù),會(huì)根據(jù)具體的執(zhí)行情況,可以選擇是以同步還是異步的方式對(duì)回調(diào)函數(shù)進(jìn)行調(diào)用。

下面我們以 onReady(fn) 為例進(jìn)行說(shuō)明,這個(gè)函數(shù)會(huì)接收一個(gè)回調(diào)函數(shù)進(jìn)行處理。

    function onReady(fn) {
        var readyState = document.readyState;
        if (readyState === 'interactive' || readyState === 'complete') {
            fn();
        } else {
            window.addEventListener('DOMContentLoaded', fn);
        }
    }
    onReady(function () {
        console.log('DOM fully loaded and parsed');
    });
    console.log('==Starting==');

mixed-onready.js 會(huì)根據(jù)執(zhí)行時(shí) DOM 是否已經(jīng)裝載完畢來(lái)決定是對(duì)回調(diào)函數(shù)進(jìn)行同步調(diào)用還是異步調(diào)用。

如果在調(diào)用 onReady 之前 DOM 已經(jīng)載入的話::
對(duì)回調(diào)函數(shù)進(jìn)行同步調(diào)用

如果在調(diào)用onReady之前DOM還沒(méi)有載入的話::

通過(guò)注冊(cè) DOMContentLoaded 事件監(jiān)聽器來(lái)對(duì)回調(diào)函數(shù)進(jìn)行異步調(diào)用

因此,如果這段代碼在源文件中出現(xiàn)的位置不同,在控制臺(tái)上打印的 log 消息順序也會(huì)不同。

為了解決這個(gè)問(wèn)題,我們可以選擇統(tǒng)一使用異步調(diào)用的方式。

    function onReady(fn) {
        var readyState = document.readyState;
        if (readyState === 'interactive' || readyState === 'complete') {
            setTimeout(fn, 0);
        } else {
        window.addEventListener('DOMContentLoaded', fn);
        }
    }
    onReady(function () {
        console.log('DOM fully loaded and parsed');
    });
    console.log('==Starting==');

關(guān)于這個(gè)問(wèn)題,在 Effective JavaScript 的 第67 項(xiàng) 不要對(duì)異步回調(diào)函數(shù)進(jìn)行同步調(diào)用 中也有詳細(xì)介紹。

  • 絕對(duì)不能對(duì)異步回調(diào)函數(shù)(即使在數(shù)據(jù)已經(jīng)就緒)進(jìn)行同步調(diào)用。
  • 如果對(duì)異步回調(diào)函數(shù)進(jìn)行同步調(diào)用的話,處理順序可能會(huì)與預(yù)期不符,可能帶來(lái)意料之外的后果。
  • 對(duì)異步回調(diào)函數(shù)進(jìn)行同步調(diào)用,還可能導(dǎo)致棧溢出或異常處理錯(cuò)亂等問(wèn)題。
  • 如果想在將來(lái)某時(shí)刻調(diào)用異步回調(diào)函數(shù)的話,可以使用 setTimeout 等異步 API。

Effective JavaScript
— David Herman

前面我們看到的 promise.then 也屬于此類,為了避免上述中同時(shí)使用同步、異步調(diào)用可能引起的混亂問(wèn)題,Promise 在規(guī)范上規(guī)定 Promise只能使用異步調(diào)用方式 。

最后,如果將上面的 onReady 函數(shù)用 Promise 重寫的話,代碼如下面所示。

    function onReadyPromise() {
        return new Promise(function (resolve, reject) {
           var readyState = document.readyState;
            if (readyState === 'interactive' || readyState === 'complete') {
                resolve();
            } else {
            window.addEventListener('DOMContentLoaded', resolve);
            }
        });
    }
    onReadyPromise().then(function () {
        console.log('DOM fully loaded and parsed');
    });
    console.log('==Starting==');

由于 Promise 保證了每次調(diào)用都是以異步方式進(jìn)行的,所以我們?cè)趯?shí)際編碼中不需要調(diào)用 setTimeout 來(lái)自己實(shí)現(xiàn)異步調(diào)用。

2.4. Promise#then

在前面的章節(jié)里我們對(duì) Promise 基本的實(shí)例方法 thencatch 的使用方法進(jìn)行了說(shuō)明。

這其中,我想大家已經(jīng)認(rèn)識(shí)了 .then().catch() 這種鏈?zhǔn)椒椒ǖ膶懛?,其?shí)在 Promise 里可以將任意個(gè)方法連在一起作為一個(gè)方法鏈(method chain)。

promise 可以寫成方法鏈的形式

    aPromise.then(function taskA(value){
    // task A
    }).then(function taskB(vaue){
    // task B
    }).catch(function onRejected(error){
        console.log(error);
    });

如果把在 then 中注冊(cè)的每個(gè)回調(diào)函數(shù)稱為 task 的話,那么我們就可以通過(guò) Promise 方法鏈方式來(lái)編寫能以 taskA -> task B 這種流程進(jìn)行處理的邏輯了。

Promise 方法鏈這種叫法有點(diǎn)長(zhǎng)(其實(shí)是在日語(yǔ)里有點(diǎn)長(zhǎng),中文還可以 --譯者注),因此后面我們會(huì)簡(jiǎn)化為 promise chain 這種叫法。

Promise 之所以適合編寫異步處理較多的應(yīng)用,promise chain 可以算得上是其中的一個(gè)原因吧。

在本小節(jié),我們將主要針對(duì)使用 then 的 promise chain 的行為和流程進(jìn)行學(xué)習(xí)。

2.4.1. promise chain

在第一章 promise chain 里我們看到了一個(gè)很簡(jiǎn)單的 then → catch 的例子,如果我們將方法鏈的長(zhǎng)度變得更長(zhǎng)的話,那在每個(gè) promise 對(duì)象中注冊(cè)的 onFulfilled 和 onRejected 將會(huì)怎樣執(zhí)行呢?

promise chain - 即方法鏈越短越好。 在這個(gè)例子里我們是為了方便說(shuō)明才選擇了較長(zhǎng)的方法鏈。

我們先來(lái)看看下面這樣的 promise chain。

    function taskA() {
        console.log("Task A");
    }
    function taskB() {
        console.log("Task B");
    }
    function onRejected(error) {
        console.log("Catch Error: A or B", error);
    }
    function finalTask() {
        console.log("Final Task");
    }

    var promise = Promise.resolve();
        promise
        .then(taskA)
        .then(taskB)
        .catch(onRejected)
        .then(finalTask);

上面代碼中的 promise chain 的執(zhí)行流程,如果用一張圖來(lái)描述一下的話,像下面的圖那樣。

http://wiki.jikexueyuan.com/project/javascript-promise-mini-book/images/2.4.1.png" alt="picture2.4.1" />

Figure 3. promise-then-catch-flow.js 附圖

上述代碼 中,我們沒(méi)有為 then 方法指定第二個(gè)參數(shù)(onRejected),也可以像下面這樣來(lái)理解。

then::
注冊(cè) onFulfilled 時(shí)的回調(diào)函數(shù)

catch::
注冊(cè) onRejected 時(shí)的回調(diào)函數(shù)

再看一下 上面的流程圖 的話,我們會(huì)發(fā)現(xiàn) Task ATask B 都有指向 onRejected 的線出來(lái)。

這些線的意思是在 Task ATask B 的處理中,在下面的情況下就會(huì)調(diào)用 onRejected 方法。

  • 發(fā)生異常的時(shí)候
  • 返回了一個(gè) Rejected 狀態(tài)的 promise 對(duì)象

第一章 中我們已經(jīng)看到,Promise 中的處理習(xí)慣上都會(huì)采用 try-catch 的風(fēng)格,當(dāng)發(fā)生異常的時(shí)候,會(huì)被 catch 捕獲并被由在此函數(shù)注冊(cè)的回調(diào)函數(shù)進(jìn)行錯(cuò)誤處理。

另一種異常處理策略是通過(guò) 返回一個(gè) Rejected 狀態(tài)的 promise 對(duì)象 來(lái)實(shí)現(xiàn)的,這種方法不通過(guò)使用 throw 就能在 promise chain 中對(duì) onRejected 進(jìn)行調(diào)用。

關(guān)于這種方法由于和本小節(jié)關(guān)系不大就不在這里詳述了,大家可以參考一下第4章 使用reject而不是throw 中的內(nèi)容。

此外在 promise chain中,由于在 onRejectedFinal Task 后面沒(méi)有 catch 處理了,因此在這兩個(gè) Task 中如果出現(xiàn)異常的話將不會(huì)被捕獲,這點(diǎn)需要注意一下。

下面我們?cè)賮?lái)看一個(gè)具體的關(guān)于 Task A -> onRejected 的例子。

Task A 產(chǎn)生異常的例子

http://wiki.jikexueyuan.com/project/javascript-promise-mini-book/images/4.png" alt="picture4" />

Figure 4. Task A 產(chǎn)生異常時(shí)的示意圖

將上面流程寫成代碼的話如下所示。

    function taskA() {
        console.log("Task A");
        throw new Error("throw Error @ Task A")
    }
    function taskB() {
        console.log("Task B");// 不會(huì)被調(diào)用
    }
    function onRejected(error) {
        console.log(error);// => "throw Error @ Task A"
    }
    function finalTask() {
        console.log("Final Task");
    }

    var promise = Promise.resolve();
    promise
        .then(taskA)
        .then(taskB)
        .catch(onRejected)
        .then(finalTask);

執(zhí)行這段代碼我們會(huì)發(fā)現(xiàn) Task B 是不會(huì)被調(diào)用的。

在本例中我們?cè)?taskA 中使用了 throw 方法故意制造了一個(gè)異常。但在實(shí)際中想主動(dòng)進(jìn)行 onRejected 調(diào)用的時(shí)候,應(yīng)該返回一個(gè) Rejected 狀態(tài)的 promise 對(duì)象。關(guān)于這種兩種方法的異同,請(qǐng)參考 使用 reject 而不是 throw 中的講解。

2.4.2. promise chain 中如何傳遞參數(shù)

前面例子中的 Task 都是相互獨(dú)立的,只是被簡(jiǎn)單調(diào)用而已。

這時(shí)候如果 Task A 想給 Task B 傳遞一個(gè)參數(shù)該怎么辦呢?

答案非常簡(jiǎn)單,那就是在 Task A 中 return 的返回值,會(huì)在 Task B 執(zhí)行時(shí)傳給它。

我們還是先來(lái)看一個(gè)具體例子吧。

    function doubleUp(value) {
        return value * 2;
    }
    function increment(value) {
        return value + 1;
    }
    function output(value) {
        console.log(value);// => (1 + 1) * 2
    }

    var promise = Promise.resolve(1);
    promise
        .then(increment)
        .then(doubleUp)
        .then(output)
        .catch(function(error){
            // promise chain中出現(xiàn)異常的時(shí)候會(huì)被調(diào)用
            console.error(error);
        });

這段代碼的入口函數(shù)是 Promise.resolve(1); ,整體的 promise chain 執(zhí)行流程如下所示。

  1. Promise.resolve(1); 傳遞 1 給 increment 函數(shù)
  2. 函數(shù) increment 對(duì)接收的參數(shù)進(jìn)行 +1 操作并返回(通過(guò) return
  3. 這時(shí)參數(shù)變?yōu)?2,并再次傳給 doubleUp 函數(shù)
  4. 最后在函數(shù) output 中打印結(jié)果

http://wiki.jikexueyuan.com/project/javascript-promise-mini-book/images/5.png" alt="picture5" />

Figure 5. promise-then-passing-value.js 示意圖

每個(gè)方法中 return 的值不僅只局限于字符串或者數(shù)值類型,也可以是對(duì)象或者 promise 對(duì)象等復(fù)雜類型。

return 的值會(huì)由 Promise.resolve(return 的返回值); 進(jìn)行相應(yīng)的包裝處理,因此不管回調(diào)函數(shù)中會(huì)返回一個(gè)什么樣的值,最終 then 的結(jié)果都是返回一個(gè)新創(chuàng)建的 promise 對(duì)象。

關(guān)于這部分內(nèi)容可以參考 專欄: 每次調(diào)用then都會(huì)返回一個(gè)新創(chuàng)建的promise對(duì)象 ,那里也對(duì)一些常見錯(cuò)誤進(jìn)行了介紹。

也就是說(shuō), Promise#then 不僅僅是注冊(cè)一個(gè)回調(diào)函數(shù)那么簡(jiǎn)單,它還會(huì)將回調(diào)函數(shù)的返回值進(jìn)行變換,創(chuàng)建并返回一個(gè) promise 對(duì)象。

2.5. Promise#catch

在前面的 Promise#then 的章節(jié)里,我們已經(jīng)簡(jiǎn)單地使用了 Promise#catch 方法。

這里我們?cè)僬f(shuō)一遍,實(shí)際上 Promise#catch 只是 promise.then(undefined, onRejected); 方法的一個(gè)別名而已。 也就是說(shuō),這個(gè)方法用來(lái)注冊(cè)當(dāng) promise 對(duì)象狀態(tài)變?yōu)?Rejected 時(shí)的回調(diào)函數(shù)。

關(guān)于如何根據(jù)場(chǎng)景使用 Promise#thenPromise#catch 可以參考 then or catch? 中介紹的內(nèi)容。

2.5.1. IE8 的問(wèn)題

http://wiki.jikexueyuan.com/project/javascript-promise-mini-book/images/5.1.png" alt="picture5.1" />

上面的這張圖,是下面這段代碼在使用 polyfill 的情況下在個(gè)瀏覽器上執(zhí)行的結(jié)果。

polyfill 是一個(gè)支持在不具備某一功能的瀏覽器上使用該功能的 Library。 這里我們使用的例子則來(lái)源于 jakearchibald/es6-promise 。

Promise#catch 的運(yùn)行結(jié)果

    var promise = Promise.reject(new Error("message"));
    promise.catch(function (error) {
    console.error(error);
    });

如果我們?cè)诟鞣N瀏覽器中執(zhí)行這段代碼,那么在 IE8 及以下版本則會(huì)出現(xiàn) identifier not found 的語(yǔ)法錯(cuò)誤。

這是怎么回事呢? 實(shí)際上這和 catchECMAScript 的 保留字 (Reserved Word)有關(guān)。

在 ECMAScript 3 中保留字是不能作為對(duì)象的屬性名使用的。 而 IE8 及以下版本都是基于 ECMAScript 3 實(shí)現(xiàn)的,因此不能將 catch 作為屬性來(lái)使用,也就不能編寫類似 promise.catch() 的代碼,因此就出現(xiàn)了 identifier not found 這種語(yǔ)法錯(cuò)誤了。

而現(xiàn)在的瀏覽器都是基于 ECMAScript 5 的,而在 ECMAScript 5中保留字都屬于 IdentifierName ,也可以作為屬性名使用了。

在 ECMAScript5 中保留字也不能作為 Identifiehttp://es5.github.io/#x7.6r 即變量名或方法名使用。 如果我們定義了一個(gè)名為 for 的變量的話,那么就不能和循環(huán)語(yǔ)句的 for 區(qū)分了。 而作為屬性名的話,我們還是很容易區(qū)分 object.forfor當(dāng)然,我們也可以想辦法回避這個(gè) ECMAScript 3保留字帶來(lái)的問(wèn)題。

點(diǎn)標(biāo)記法(dot notation) 要求對(duì)象的屬性必須是有效的標(biāo)識(shí)符(在 ECMAScript 3中則不能使用保留字),

但是使用 中括號(hào)標(biāo)記法(bracket notation) 的話,則可以將非合法標(biāo)識(shí)符作為對(duì)象的屬性名使用。

也就是說(shuō),上面的代碼如果像下面這樣重寫的話,就能在 IE8 及以下版本的瀏覽器中運(yùn)行了(當(dāng)然還需要 polyfill)。 的,仔細(xì)想想我們就應(yīng)該能接受將保留字作為屬性名來(lái)使用了。

解決 Promise#catch 標(biāo)識(shí)符沖突問(wèn)題

    var promise = Promise.reject(new Error("message"));
    promise["catch"](function (error) {
        console.error(error);
    });

或者我們不單純的使用 catch ,而是使用 then 也是可以避免這個(gè)問(wèn)題的。

使用 Promise#then 代替 Promise#catch

    var promise = Promise.reject(new Error("message"));
    promise.then(undefined, function (error) {
        console.error(error);
    });

由于 catch 標(biāo)識(shí)符可能會(huì)導(dǎo)致問(wèn)題出現(xiàn),因此一些類庫(kù)(Library)也采用了 caught 作為函數(shù)名,而函數(shù)要完成的工作是一樣的。

而且很多壓縮工具自帶了將 promise.catch 轉(zhuǎn)換為 promise["catch"] 的功能, 所以可能不經(jīng)意之間也能幫我們解決這個(gè)問(wèn)題。

如果各位讀者需要支持 IE8 及以下版本的瀏覽器的話,那么一定要將這個(gè) catch 問(wèn)題牢記在心中。

2.6. 專欄: 每次調(diào)用 then 都會(huì)返回一個(gè)新創(chuàng)建的 promise 對(duì)象

從代碼上乍一看, aPromise.then(...).catch(...) 像是針對(duì)最初的 aPromise 對(duì)象進(jìn)行了一連串的方法鏈調(diào)用。

然而實(shí)際上不管是 then 還是 catch 方法調(diào)用,都返回了一個(gè)新的 promise 對(duì)象。

下面我們就來(lái)看看如何確認(rèn)這兩個(gè)方法返回的到底是不是新的 promise 對(duì)象。

    var aPromise = new Promise(function (resolve) {
        resolve(100);
    });
    var thenPromise = aPromise.then(function (value) {
        console.log(value);
    });
    var catchPromise = thenPromise.catch(function (error) {
        console.error(error);
    });
    console.log(aPromise !== thenPromise); // => true
    console.log(thenPromise !== catchPromise);// => true

=== 是嚴(yán)格相等比較運(yùn)算符,我們可以看出這三個(gè)對(duì)象都是互不相同的,這也就證明了 thencatch 都返回了和調(diào)用者不同的 promise 對(duì)象。

http://wiki.jikexueyuan.com/project/javascript-promise-mini-book/images/2.6.1.png" alt="picture2.6.1" />

我們?cè)趯?duì) Promise 進(jìn)行擴(kuò)展的時(shí)候需要牢牢記住這一點(diǎn),否則稍不留神就有可能對(duì)錯(cuò)誤的 promise 對(duì)象進(jìn)行了處理。

如果我們知道了 then 方法每次都會(huì)創(chuàng)建并返回一個(gè)新的 promise 對(duì)象的話,那么我們就應(yīng)該不難理解下面代碼中對(duì) then 的使用方式上的差別了。

    // 1: 對(duì)同一個(gè)promise對(duì)象同時(shí)調(diào)用 `then` 方法
    var aPromise = new Promise(function (resolve) {
        resolve(100);
    });
    aPromise.then(function (value) {
        return value * 2;
    });
    aPromise.then(function (value) {
        return value * 2;
    });
    aPromise.then(function (value) {
    console.log("1: " + value); // => 100
    })

    // vs

    // 2: 對(duì) `then` 進(jìn)行 promise chain 方式進(jìn)行調(diào)用
    var bPromise = new Promise(function (resolve) {
        resolve(100);
    });
    bPromise.then(function (value) {
        return value * 2;
    }).then(function (value) {
        return value * 2;
    }).then(function (value) {
    console.log("2: " + value); // => 100 * 2 * 2
    });

第 1 種寫法中并沒(méi)有使用 promise 的方法鏈方式,這在 Promise 中是應(yīng)該極力避免的寫法。這種寫法中的 then 調(diào)用幾乎是在同時(shí)開始執(zhí)行的,而且傳給每個(gè) then 方法的 value 值都是 100 。

第 2 中寫法則采用了方法鏈的方式將多個(gè) then 方法調(diào)用串連在了一起,各函數(shù)也會(huì)嚴(yán)格按照 resolve → then → then → then 的順序執(zhí)行,并且傳給每個(gè) then 方法的 value 的值都是前一個(gè) promise 對(duì)象通過(guò) return 返回的值。

下面是一個(gè)由方法 1 中的 then 用法導(dǎo)致的比較容易出現(xiàn)的很有代表性的反模式的例子。

? then 的錯(cuò)誤使用方法

     function badAsyncCall() {
        var promise = Promise.resolve();
        promise.then(function() {
            // 任意處理
            return newVar;
        });
        return promise;
    }

這種寫法有很多問(wèn)題,首先在 promise.then 中產(chǎn)生的異常不會(huì)被外部捕獲,此外,也不能得到 then 的返回值,即使其有返回值。

由于每次 promise.then 調(diào)用都會(huì)返回一個(gè)新創(chuàng)建的 promise 對(duì)象,因此需要像上述方式 2 那樣,采用 promise chain 的方式將調(diào)用進(jìn)行鏈?zhǔn)交?,修改后的代碼如下所示。

then 返回返回新創(chuàng)建的 promise 對(duì)象

    function anAsyncCall() {
        var promise = Promise.resolve();
        return promise.then(function() {
            // 任意處理
            return newVar;
        });
    }

關(guān)于這些反模式,詳細(xì)內(nèi)容可以參考 Promise Anti-patterns 。

這種函數(shù)的行為貫穿在 Promise 整體之中, 包括我們后面要進(jìn)行說(shuō)明的 Promise.allPromise.race ,他們都會(huì)接收一個(gè) promise 對(duì)象為參數(shù),并返回一個(gè)和接收參數(shù)不同的、新的 promise 對(duì)象。

2.7. Promise 和數(shù)組

到目前為止我們已經(jīng)學(xué)習(xí)了如何通過(guò) .then.catch 來(lái)注冊(cè)回調(diào)函數(shù),這些回調(diào)函數(shù)會(huì)在 promise 對(duì)象變?yōu)?FulFilled 或 Rejected 狀態(tài)之后被調(diào)用。

如果只有一個(gè) promise 對(duì)象的話我們可以像前面介紹的那樣編寫代碼就可以了,如果要在多個(gè) promise 對(duì)象都變?yōu)?FulFilled 狀態(tài)的時(shí)候才要進(jìn)行某種處理話該如何操作呢?

我們以當(dāng)所有 XHR(異步處理)全部結(jié)束后要進(jìn)行某操作為例來(lái)進(jìn)行說(shuō)明。

各位讀者現(xiàn)在也許有點(diǎn)難以在大腦中描繪出這么一種場(chǎng)景,我們可以先看一下下面使用了普通的回調(diào)函數(shù)風(fēng)格的 XHR 處理代碼。

2.7.1. 通過(guò)回調(diào)方式來(lái)進(jìn)行多個(gè)異步調(diào)用

    function getURLCallback(URL, callback) {
    var req = new XMLHttpRequest();
    req.open('GET', URL, true);
    req.onload = function () {
    if (req.status === 200) {
    callback(null, req.responseText);
    } else {
    callback(new Error(req.statusText), req.response);
    }
    };
    req.onerror = function () {
    callback(new Error(req.statusText));
    };
    req.send();
    }
    // <1> 對(duì)JSON數(shù)據(jù)進(jìn)行安全的解析
    function jsonParse(callback, error, value) {
    if (error) {
    callback(error, value);
    } else {
    try {
    var result = JSON.parse(value);
    callback(null, result);
    } catch (e) {
    callback(e, value);
    }
    }
    }
    // <2> 發(fā)送XHR請(qǐng)求
    var request = {
    comment: function getComment(callback) {
    return getURLCallback('http://azu.github.io/promises-book/json/comment.json', jsonParse.bind(null, callback));
    },
    people: function getPeople(callback) {
    return getURLCallback('http://azu.github.io/promises-book/json/people.json', jsonParse.bind(null, callback));
    }
    };
    // <3> 啟動(dòng)多個(gè)XHR請(qǐng)求,當(dāng)所有請(qǐng)求返回時(shí)調(diào)用callback
    function allRequest(requests, callback, results) {
    if (requests.length === 0) {
    return callback(null, results);
    }
    var req = requests.shift();
    req(function (error, value) {
    if (error) {
    callback(error, value);
    } else {
    results.push(value);
    allRequest(requests, callback, results);
    }
    });
    }
    function main(callback) {
    allRequest([request.comment, request.people], callback, []);
    }
    // 運(yùn)行的例子
    main(function(error, results){
    if(error){
    return console.error(error);
    }
    console.log(results);
    });

這段回調(diào)函數(shù)風(fēng)格的代碼有以下幾個(gè)要點(diǎn)。

  • 直接使用 JSON.parse 函數(shù)的話可能會(huì)拋出異常,所以這里使用了一個(gè)包裝函數(shù) jsonParse
  • 如果將多個(gè) XHR 處理進(jìn)行嵌套調(diào)用的話層次會(huì)比較深,所以使用了 allRequest 函數(shù)并在其中對(duì) request 進(jìn)行調(diào)用。
  • 回調(diào)函數(shù)采用了 callback(error,value) 這種寫法,第一個(gè)參數(shù)表示錯(cuò)誤信息,第二個(gè)參數(shù)為返回值

在使用 jsonParse 函數(shù)的時(shí)候我們使用了 bind 進(jìn)行綁定,通過(guò)使用這種偏函數(shù)(Partial Function)的方式就可以減少匿名函數(shù)的使用。(如果在函數(shù)回調(diào)風(fēng)格的代碼能很好的做到函數(shù)分離的話,也能減少匿名函數(shù)的數(shù)量)

    jsonParse.bind(null, callback);
    // 可以認(rèn)為這種寫法能轉(zhuǎn)換為以下的寫法
    function bindJSONParse(error, value){
        jsonParse(callback, error, value);
    }

在這段回調(diào)風(fēng)格的代碼中,我們也能發(fā)現(xiàn)如下一些問(wèn)題。

  • 需要顯示進(jìn)行異常處理
  • 為了不讓嵌套層次太深,需要一個(gè)對(duì) request 進(jìn)行處理的函數(shù)
  • 到處都是回調(diào)函數(shù)

下面我們?cè)賮?lái)看看如何使用 Promise#then 來(lái)完成同樣的工作

2.7.2. 使用 Promise#then 同時(shí)處理多個(gè)異步請(qǐng)求

需要事先說(shuō)明的是 Promise.all 比較適合這種應(yīng)用場(chǎng)景的需求,因此我們故意采用了大量 .then 的晦澀的寫法。

使用了 .then 的話,也并不是說(shuō)能和回調(diào)風(fēng)格完全一致,大概重寫后代碼如下所示。

    function getURL(URL) {
    return new Promise(function (resolve, reject) {
    var req = new XMLHttpRequest();
    req.open('GET', URL, true);
    req.onload = function () {
    if (req.status === 200) {
    resolve(req.responseText);
    } else {
    reject(new Error(req.statusText));
    }
    };
    req.onerror = function () {
    reject(new Error(req.statusText));
    };
    req.send();
    });
    }
    var request = {
    comment: function getComment() {
    return getURL('http://azu.github.io/promises-book/json/comment.json').then(JSON.parse);
    },
    people: function getPeople() {
    return getURL('http://azu.github.io/promises-book/json/people.json').then(JSON.parse);
    }
    };
    function main() {
    function recordValue(results, value) {
    results.push(value);
    return results;
    }
    // [] 用來(lái)保存初始化的值
    var pushValue = recordValue.bind(null, []);
    return request.comment().then(pushValue).then(request.people).then(pushValue);
    }
    // 運(yùn)行的例子
    main().then(function (value) {
    console.log(value);
    }).catch(function(error){
    console.error(error);
    });

將上述代碼和回調(diào)函數(shù)風(fēng)格相比,我們可以得到如下結(jié)論。

  • 可以直接使用 JSON.parse 函數(shù)
  • 函數(shù) main() 返回 promise 對(duì)象
  • 錯(cuò)誤處理的地方直接對(duì)返回的 promise 對(duì)象進(jìn)行處理

向前面我們說(shuō)的那樣,mainthen 部分有點(diǎn)晦澀難懂。

為了應(yīng)對(duì)這種需要對(duì)多個(gè)異步調(diào)用進(jìn)行統(tǒng)一處理的場(chǎng)景,Promise 準(zhǔn)備了 Promise.allPromise.race 這兩個(gè)靜態(tài)方法。

在下面的小節(jié)中我們將對(duì)這兩個(gè)函數(shù)進(jìn)行說(shuō)明。

2.8. Promise.all

Promise.all 接收一個(gè) promise 對(duì)象的數(shù)組作為參數(shù),當(dāng)這個(gè)數(shù)組里的所有 promise 對(duì)象全部變?yōu)?resolve 或 rejec t狀態(tài)的時(shí)候,它才會(huì)去調(diào)用 .then 方法。

前面我們看到的批量獲得若干 XHR 的請(qǐng)求結(jié)果的例子,使用 Promise.all 的話代碼會(huì)非常簡(jiǎn)單。

之前例子中的 getURL 返回了一個(gè) promise 對(duì)象,它封裝了 XHR 通信的實(shí)現(xiàn)。 向 Promise.all 傳遞一個(gè)由封裝了 XHR 通信的 promise 對(duì)象數(shù)組的話,則只有在全部的 XHR 通信完成之后(變?yōu)?FulFilled 或 Rejected 狀態(tài))之后,才會(huì)調(diào)用 .then 方法。

    function getURL(URL) {
    return new Promise(function (resolve, reject) {
    var req = new XMLHttpRequest();
    req.open('GET', URL, true);
    req.onload = function () {
    if (req.status === 200) {
    resolve(req.responseText);
    } else {
    reject(new Error(req.statusText));
    }
    };
    req.onerror = function () {
    reject(new Error(req.statusText));
    };
    req.send();
    });
    }
    var request = {
    comment: function getComment() {
    return getURL('http://azu.github.io/promises-book/json/comment.json').then(JSON.parse);
    },
    people: function getPeople() {
    return getURL('http://azu.github.io/promises-book/json/people.json').then(JSON.parse);
    }
    };
    function main() {
    return Promise.all([request.comment(), request.people()]);
    }
    // 運(yùn)行示例
    main().then(function (value) {
    console.log(value);
    }).catch(function(error){
    console.log(error);
    });

這個(gè)例子的執(zhí)行方法和 前面的例子 一樣。 不過(guò) Promise.all 在以下幾點(diǎn)和之前的例子有所不同。

  • main 中的處理流程顯得非常清晰
  • Promise.all` 接收 promise 對(duì)象組成的數(shù)組作為參數(shù)
    Promise.all([request.comment(), request.people()]);

在上面的代碼中,request.comment()request.people() 會(huì)同時(shí)開始執(zhí)行,而且每個(gè) promise 的結(jié)果(resolve 或 reject 時(shí)傳遞的參數(shù)值),和傳遞給 Promise.all 的 promise 數(shù)組的順序是一致的。

也就是說(shuō),這時(shí)候 .then 得到的 promise 數(shù)組的執(zhí)行結(jié)果的順序是固定的,即 [comment, people]。

    main().then(function (results) {
        console.log(results); // 按照[comment, people]的順序
    });

如果像下面那樣使用一個(gè)計(jì)時(shí)器來(lái)計(jì)算一下程序執(zhí)行時(shí)間的話,那么就可以非常清楚的知道傳遞給 Promise.all 的 promise 數(shù)組是同時(shí)開始執(zhí)行的。


    // `delay`毫秒后執(zhí)行resolve
    function timerPromisefy(delay) {
    return new Promise(function (resolve) {
    setTimeout(function () {
    resolve(delay);
    }, delay);
    });
    }
    var startDate = Date.now();
    // 所有promise變?yōu)閞esolve后程序退出
    Promise.all([
    timerPromisefy(1),
    timerPromisefy(32),
    timerPromisefy(64),
    timerPromisefy(128)
    ]).then(function (values) {
    console.log(Date.now() - startDate + 'ms');
    // 約128ms
    console.log(values);// [1,32,64,128]
    });

timerPromisefy 會(huì)每隔一定時(shí)間(通過(guò)參數(shù)指定)之后,返回一個(gè) promise 對(duì)象,狀態(tài)為 FulFilled,其狀態(tài)值為傳給 timerPromisefy 的參數(shù)。

而傳給 Promise.all 的則是由上述 promise 組成的數(shù)組。

    var promises = [
        timerPromisefy(1),
        timerPromisefy(32),
        timerPromisefy(64),
        timerPromisefy(128)

這時(shí)候,每隔1, 32, 64, 128 ms都會(huì)有一個(gè) promise 發(fā)生 resolve 行為。

也就是說(shuō),這個(gè) promise 對(duì)象數(shù)組中所有 promise 都變?yōu)?resolve 狀態(tài)的話,至少需要 128 ms。實(shí)際我們計(jì)算一下 Promise.all 的執(zhí)行時(shí)間的話,它確實(shí)是消耗了 128 ms 的時(shí)間。

從上述結(jié)果可以看出,傳遞給 Promise.all 的 promise 并不是一個(gè)個(gè)的順序執(zhí)行的,而是同時(shí)開始、并行執(zhí)行的。

如果這些 promise 全部串行處理的話,那么需要 等待 1 ms → 等待 32 ms → 等待 64 ms → 等待 128 ms ,全部執(zhí)行完畢需要 225 ms 的時(shí)間。

要想了解更多關(guān)于如何使用 Promise 進(jìn)行串行處理的內(nèi)容,可以參考第 4 章的 Promise 中的串行處理中的介紹。

2.9. Promise.race

接著我們來(lái)看看和 Promise.all 類似的對(duì)多個(gè) promise 對(duì)象進(jìn)行處理的 Promise.race 方法。

它的使用方法和 Promise.all 一樣,接收一個(gè) promise 對(duì)象數(shù)組為參數(shù)。

Promise.all 在接收到的所有的對(duì)象 promise 都變?yōu)?FulFilled 或者 Rejected 狀態(tài)之后才會(huì)繼續(xù)進(jìn)行后面的處理, 與之相對(duì)的是 Promise.race 只要有一個(gè) promise 對(duì)象進(jìn)入 FulFilled 或者 Rejected 狀態(tài)的話,就會(huì)繼續(xù)進(jìn)行后面的處理。

Promise.all 時(shí)的例子一樣,我們來(lái)看一個(gè)帶計(jì)時(shí)器的 Promise.race 的使用例子。

    // `delay`毫秒后執(zhí)行resolve
    function timerPromisefy(delay) {
        return new Promise(function (resolve) {
            setTimeout(function () {
                resolve(delay);
             }, delay);
        });
    }
    // 任何一個(gè)promise變?yōu)閞esolve或reject 的話程序就停止運(yùn)行
    Promise.race([
        timerPromisefy(1),
        timerPromisefy(32),
        timerPromisefy(64),
        timerPromisefy(128)
    ]).then(function (value) {
        console.log(value);    // => 1
    });

上面的代碼創(chuàng)建了 4 個(gè) promise 對(duì)象,這些 promise 對(duì)象會(huì)分別在 1 ms,32 ms,64 ms 和 128 ms 后變?yōu)榇_定狀態(tài),即 FulFilled,并且在第一個(gè)變?yōu)榇_定狀態(tài)的 1 ms 后, .then 注冊(cè)的回調(diào)函數(shù)就會(huì)被調(diào)用,這時(shí)候確定狀態(tài)的 promise 對(duì)象會(huì)調(diào)用 resolve(1) 因此傳遞給 value 的值也是 1,控制臺(tái)上會(huì)打印出 1 來(lái)。

下面我們?cè)賮?lái)看看在第一個(gè) promise 對(duì)象變?yōu)榇_定(FulFilled)狀態(tài)后,它之后的 promise 對(duì)象是否還在繼續(xù)運(yùn)行。

    var winnerPromise = new Promise(function (resolve) {
            setTimeout(function () {
                console.log('this is winner');
                resolve('this is winn
上一篇:API Reference下一篇:什么是 Promise