最認真的大白話講ES6-Promise

Promise是面試中最多見的問題之一,也是ES6中頗有用、很核心的一個新特性。尤爲在現在異步操做愈來愈多、愈來愈複雜的狀況下,Prmoise更是顯示出了它強大而又優雅的本質。這篇文章,咱們來系統地講解一下Promise相關的核心知識點。git

本文已同步至個人我的主頁。歡迎訪問查看更多內容!若有錯誤或不足,歡迎隨時探討交流。謝謝你們的關注和支持!github

本文,咱們按照如下的思路來逐步深刻Promise:web

  • 1、什麼是Promise?(What)
  • 2、爲何須要Promise?或者說Promise的用途是什麼?(Why)
  • 3、如何使用Promise?(How)
  • 4、Promise的其餘方法。

1、什麼是Promise

Promise是一種異步編程的解決方案,它能夠將異步操做以同步的流程表達出來,它比傳統的使用回調函數和事件來處理異步問題更加合理,更符合人們線性處理問題的邏輯。 從語法上說,Promise是一個對象,它裏面保存着一個未來纔會發生的事情(通常是一個異步操做)的狀態和結果。面試

聽起來,有些抽象,全是概念性的東西。那接下來咱們看看爲何ES6中會出現Promise?經過具體示例,能夠幫助咱們更好的理解什麼是Promise編程

2、爲何須要Promise?它的用途是什麼?

在ES6出現Promise以前,咱們要處理一個異步請求,一般是這樣的:數組

// 利用回調函數來處理異步請求結果
// 不少異步請求方法也會設計一些事件,在事件中處理異步請求結果
asyncRequest(function(resData) {
    // 處理請求結果
});
複製代碼

這樣看着沒什麼問題,但需求老是各類各樣甚至是變態的。若是咱們須要在第一個請求返回結果後再發起第二個請求呢?再若是,第二個請求結果返回後後,咱們須要再發起第三個請求呢?以後,再是第四個...第五個......此時,代碼應該會變成這樣:promise

asyncRequest1(function(resData1) {
    asyncRequest2(function(resData2) {
        asyncRequest3(function(resData3) {
            asyncRequest4(function(resData4) {
                asyncRequest5(function(resData5) {
                    // ......
                    // 處理請求結果
                });
            });
        });
    });
});
複製代碼

這時,代碼嵌套層次太深,再加上每次請求結束咱們應該還須要作一些適當的邏輯處理,這樣每一個處理請求結果的地方還須要額外的代碼,這樣整個代碼塊顯得很臃腫,一點也不優雅!最主要的是,這樣的代碼很容易出錯,並且出錯後不容易定位錯誤,閱讀和維護起來十分費勁。bash

這就是異步編程最讓人頭疼和無語的地方:因爲異步操做嵌套層次過深而致使的「回調地獄」!異步

出現這種狀況,就須要思考新的異步編程的處理方法。有沒有什麼方法能在知足上面例子的需求的同時又能解決這種嵌套式的回調地獄呢?能不能不使用嵌套式回調,而使用鏈式回調呢?確定是有的,這也就是Promise出現的緣由。同時,能不能最好不使用回調的方式來處理異步請求呢?固然也是能夠的,這就是咱們後面文章會講的async/awiatasync

咱們來看看,上面的例子,若是使用Promise來實現是什麼樣子?應該是這樣:

new Promise(asyncRequest1)
    .then(asyncRequest2(resData1))
    .then(asyncRequest3(resData2))
    .then(asyncRequest4(resData3))
    .then(asyncRequest5(resData4))
    .catch(handleError(errorMsg))
複製代碼

上面的例子,只有每一個一步請求asyncRequest成功返回結果,纔會進入下一個.then()方法中,從而進行下一個異步請求......以此類推。當任何一個請求出錯時,就會進入.catch()方法中,能夠在這裏處理錯誤。這樣的鏈式回調,既知足前面例子的需求,同時又避免了嵌套回調,從而避免了「回調地獄」的出現。

這裏具體的語法看不懂,沒關係!不要慌!這個例子只是爲了說明Promise是如何用鏈式回調來解決嵌套回調地獄的。接下來,咱們就來講說如何使用Promise,講講它的基本語法。

3、如何使用Promise(基本語法)

一、建立一個Promise對象實例

ES6中規定,Promise是一個構造函數,能夠用來實例化一個Promise對象。下面是一個簡單的例子:

// Promise構造函數接收一個函數做爲參數
let promise = new Promise(Function);
複製代碼

二、Promise的狀態及其改變

文章最開始介紹什麼是Promise的時候說過:Promise是一個對象,它裏面保存着一個未來纔會發生的事情(通常是一個異步操做)的狀態和結果。

咱們先來看看Promise表明的異步操做的狀態有哪幾種?——一共只有三種狀態:

  1. pending(進行中)
  2. fulfilled(已成功)
  3. rejected(已失敗)

這三種狀態,不會共存,Promise只會處於其中某一種狀態。當異步請求開始而且未結束(沒有返回結果)時,處於pending狀態。當異步請求返回結果後,能夠根據請求返回的結果將Promise的狀態修改成fulfilled或者rejected。而且,一旦Promise的狀態第一次改變,就不再能更改成其它任何狀態。因此,Promise的狀態改變過程只有兩種狀況:

  1. pending --> fulfilled(進行中 --> 已成功)
  2. pending --> rejected(進行中 --> 已失敗)

那麼,如何修改Promise的狀態呢?這就須要瞭解調用Promise構造函數時,傳遞給構造函數的Function參數了。Promise會爲這個函數設置兩個參數,resolvereject。這兩個參數是兩個函數,由JavaScript引擎提供,不用本身部署。

resolve()函數,能夠將Promise的狀態由pending改變爲fulfilledreject()函數,能夠將Promise的狀態由pending改變爲rejected

這裏有兩點須要注意的地方!!

  1. Promise內部只有用resolve()reject()才能改變它的狀態。return任何值(包括一個Error實例)都不會改變它的狀態。throw任何值,還會引發報錯!
  2. resolve()reject()return的意義不一樣。他們只是改變了Promise的狀態,並不會結束代碼執行。也就是說resolve()reject()以後的代碼依舊會執行。(雖然不建議在他們後面再有代碼出現)
  3. 在定義Promise時,參數函數中異步操做以外的同步代碼都會當即執行。

來看一個示例,簡單明瞭地理解上面的文字。

let promise = new Promise(function(resolve, reject) {
    // 下面兩行代碼會當即執行,不會等待異步操做結果返回、狀態改變
    let a = '123';
    console.log(a); // '123'

    // 一個異步請求
    asyncRequest(function(resData) {
        if (/* 異步操做成功 */){
            // 將Promise的狀態改成fulfilled(已成功)
            resolve(resData);   // resData通常是異步操做的結果
        } else {
            // 將Promise的狀態改成rejected(已失敗)
            reject(resData);    // resData通常是一些錯誤信息
        }
    });
});
複製代碼

上面的例子,在Promise內部發起了一個異步請求,當請求完成,拿到返回值resData時,咱們能夠根據具體的業務需求修改Promise的對應狀態。

三、Promise保存異步操做的結果

細心的同窗會發現,上面的例子中,咱們在resolve()reject()函數中傳入了參數resData,這是在幹什麼?還記得麼?Promise不只能保存異步操做的狀態,還能保存異步操做的結果。咱們將將異步操做的結果resData傳給這兩個函數,就是將其保存到了Promise對象中。

四、獲取Promise中的狀態和結果(.then()/.catch())

那麼,Prmoise對象中保存了異步操做的最終狀態和結果,咱們如何獲取呢?換句話說,咱們怎麼知道異步操做的狀態和結果分別是什麼呢?

其實,每一個Promise的對象實例都會有一個.then().catch()方法,這兩個方法都接收一個函數做爲參數,這個函數會被Promise傳入一個參數,這個參數就是傳入resolve()reject()方法中的異步請求的結果(上個例子中的resData)。當Promise內部狀態變爲fulfilled時,就會進入.then()方法中,執行裏面的回調函數。同理,當Promise內部狀態變爲rejected時,就會進入.catch()方法中,執行裏面的回調函數。

/***接着上面例子***/
promise.then(function(resData) {
    // promise狀態變爲fulfilled,執行這裏
    console.log(resData);
}).catch(function(resData) {
    // promise狀態變爲rejected,執行這裏
    console.log(resData);
});
複製代碼

像上面這樣,當執行進入.then()中,就說明Promise的狀態是fulfilled。進入.catch()中,就說明狀態是rejected,通常會在這裏進行錯誤處理。同時,異步操做的結果會被傳入定義在.then().catch()內部的函數中,咱們能夠直接訪問使用。

五、.then()/.catch()的鏈式調用

**在.then()/.catch()的返回值依舊是一個Promise實例。**也就是說,在.then()/.catch()return任何值,都會被轉化成一個Promise實例。因此.then()後面能夠鏈式繼續調用.then()/.catch.catch()後面一樣也能夠。因而,就有可能出現下面這樣的代碼:

// 這樣的代碼是徹底沒有問題的。
promise.then(function(resData) {
    // 一些代碼
}).then(function(resData) {
    // 一些代碼
}).catch(function(error) {
    // 一些代碼
}).then(function(resData) {
    // 一些代碼
}).catch(function(error) {
    // 一些代碼
});
複製代碼

這裏有一些須要注意的地方!!

  1. 前一個.then()return任何值(包括一個Error實例),都會進入後面最鄰近的.then()
  2. 前一個.then()throw任何值或者內部代碼報錯,都會進入後面最鄰近的.catch()
  3. 同理,.catch()中狀況與.then()徹底一致。

4、Promise的其餘方法

一、 Promise.resolve()

Promise.resolve方法接收一個任意值做爲參數,能夠將其轉換爲Promise對象。

該方法對參數的處理,能夠分爲如下四種不一樣的狀況:

(1)參數是一個Promise實例

此時,Promise.resolve方法將不會作任何轉換,直接原封不動的返回這個實例。

(2)參數是一個thenable對象

thenable對象是指對象內部實現了then方法的對象。此時,Promise.resolve方法會先將該對象轉換爲Promise對象,而後當即執行參數對象本身的then方法。

最終,轉換成的Promise對象的狀態徹底依賴於它內部then方法的具體實現,不必定是fulfilled狀態,也有多是rejected

// 定義一個thenable對象
let thenable = {
    then: function(resolve, reject) {
        resolve(42);
        // 若是換成執行下面一行代碼,後面將會進入.catch()中
        // reject('error');
    }
};
let p1 = Promise.resolve(thenable);
p1.then(function(value) {
    console.log(value);       // 42
}).catch(function(value) {
    console.log(value);     // 'error'
});
複製代碼
(3)參數不是thenable對象,或者不是一個對象

若是參數不是thenable對象,或者不是一個對象,Promise.resolve方法返回一個新的Promise對象,狀態爲fulfilled,對象保存的值就是這個參數值。

Promise.resolve('foo')
// 等價於
new Promise(resolve => resolve('foo'));

let p = Promise.resolve('Hello');
p.then(function (s){
    console.log(s);   // 'Hello'
});
複製代碼
(4)不傳任何參數

Promise.resolve方法容許調用時不帶參數,直接返回一個fulfilled狀態的Promise對象,對象保存的值爲undefined

let p = Promise.resolve();

p.then(function (value) {
  console.log(value);   // undefined
});
複製代碼

二、Promise.reject()

Promise.reject方法也會返回一個新的Promise實例。不論傳入的參數是什麼數據類型,有沒有thenable方法,該實例的狀態必定爲爲rejected,且返回的Promise對象中保存的值就是傳入Promise.reject方法時原封不動的參數值

例子一
let p = Promise.reject('error');
// 等同於
let p = new Promise((resolve, reject) => reject('error'));

// 例子二
let thenable = {
    then(resolve, reject) {
        /**
         * 不論執行下面的哪一行,
         * 最後Promise對象的狀態都是rejected,
         * 都會進入.catch中
         *
        reject('error');
        // resolve('fulfilled');
    }
};

Promise.reject(thenable).then(data => {
    // 不會進入這裏!!
    console.log('進入then!');
}).catch(e => {
    console.log('進入catch!');
    // !注意!這裏的e的值是傳入Promise.reject()方法的thenable對象
    console.log(e === thenable);  // true
})
複製代碼

二、Promise.all()

Promise.all方法用於將多個Promise實例,包裝成一個新的Promise實例。

let p = Promise.all([p1, p2, p3]);
複製代碼

Promise.all方法接收一個數組做爲參數,數組元素p1/p2/p3都是Promise實例。若是不是,就會先調用Promise.resolve()方法,將參數轉爲Promise實例,再進一步處理。

最終,p的狀態由p1/p2/p3共同決定,分紅兩種狀況:

  1. 只有p1/p2/p3的狀態都變成fulfilledp的狀態纔會變成fulfilled。此時p1/p2/p3的返回值組成一個數組,傳遞給p的回調函數。
  2. 只要p1/p2/p3之中有一個被rejectedp的狀態就變成rejected。此時第一個被reject的實例的返回值,會傳遞給p的回調函數。

二、Promise.race()

Promise.race方法一樣是將多個Promise實例,包裝成一個新的Promise實例。

let p = Promise.race([p1, p2, p3]);
複製代碼

Promise.race方法接收的參數與Promise.all方法同樣,若是不是 Promise 實例,就會先調用Promise.resolve()方法,將參數轉爲 Promise 實例,再進一步處理。

上面的例子中,只要p1/p2/p3之中任意一個實例率先改變狀態,不論變爲哪一種狀態,p的狀態就跟着改變。那個率先改變狀態的Promise實例的返回值,就傳遞給p的回調函數。

友情連接

《面試精選之Promise》——這篇文章也寫得很好,本文參考借鑑了一些地方,推薦你們能夠結合着一塊兒看。

相關文章
相關標籤/搜索