這次聊聊Promise物件

騰訊雲+社群發表於2018-11-07

歡迎大家前往騰訊雲+社群,獲取更多騰訊海量技術實踐乾貨哦~

本文由前端林子發表於雲+社群專欄

Promise是CommonJS提出的一種規範,在ES6中已經原生支援Promise物件,非ES6環境可以用Bluebird等庫來支援。

0.引入

在js中任務的執行模型有兩種:同步模式和非同步模式。

同步模式:後一個任務B等待前一個任務A結束後,再執行。任務的執行順序和任務的排序順序是一致的。

非同步模式:每一個任務有一個或多個回撥函式,前一個任務A結束後,不是執行後一個任務B,而是執行任務A的回撥函式。而後一個任務B是不等任務A結束就執行。任務的執行順序,與任務的排序順序不一致。

非同步模式程式設計有四種方法:回撥函式(最基本的方法,把B寫成A的回撥函式)、事件監聽(為A繫結事件,當A發生某個事件,就執行B)、釋出/訂閱,以及本文要介紹的Promise物件。

Promise是一個用於處理非同步操作的物件,可以將回撥函式寫成鏈式呼叫的寫法,讓程式碼更優雅、流程更加清晰,讓我們可以更合理、更規範地進行非同步處理操作。它的思想是,每一個非同步任務返回一個Promise物件,該物件有一個then方法,允許指定回撥函式。

1.Promise的基本知識

1.1 三種狀態

Pending:進行中,剛建立一個Promise例項時,表示初始狀態;

resolved(fulfilled):resolve方法呼叫的時候,表示操作成功,已經完成;

Rejected:reject方法呼叫的時候,表示操作失敗;

1.2 兩個過程

這三種狀態只能從pendeng–>resolved(fulfilled),或者pending–>rejected,不能逆向轉換,也不能在resolved(fulfilled)和rejected之間轉換。並且一旦狀態改變,就不會再改變,會一直保持這個結果。

彙總上述,建立一個Promise的例項是這樣的:

//建立promise的例項
let promise = new Promise((resolve,reject)=>{
    //剛建立例項時的狀態:pending

    if(`非同步操作成功`){
        //呼叫resolve方法,狀態從pending變為fulfilled
        resolve();
    }else{
        //呼叫reject方法,狀態從pending變為rejected
        reject();
    }
});

1.3 then()

用於繫結處理操作後的處理程式,分別指定fulfilled狀態和rejected狀態的回撥函式,即它的引數是兩個函式,第一個用於處理操作成功後的業務,第二個用於處理操作失敗後的業務。

//then()
promise.then((res)=> {
    //處理操作成功後的業務(即Promise物件的狀態變為fullfilled時呼叫)
},(error)=> {
    //處理操作失敗後的業務(即Promise物件的狀態變為rejected時呼叫)
});

1.4 catch()

用於處理操作異常的程式,catch()只接受一個引數

//catch()
promise.catch((error)=> {
    //處理操作失敗後的業務
});

一般來說,建議不要在then()裡面定義rejected狀態的回撥函式,而是將then()用於處理操作成功,將catch()用於處理操作異常。因為這樣做可以捕獲then()執行中的錯誤,也更接近同步中try/catch的寫法:

//try-catch
// bad
promise.then((res)=> {
    //處理操作成功後的業務
  }, (error)=> {
    //處理操作失敗後的業務
  });

// good
promise
  .then((res)=> { 
    //處理操作成功後的業務
  })
  .catch((error)=> {
    //處理操作失敗後的業務
  });

1.5 all()

接受一個陣列作為引數,陣列的元素是Promise例項物件。只有當引數中的例項物件的狀態都為fulfilled時,Promise.all( )才會有返回。

例項程式碼(可直接在瀏覽器中開啟):

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <title>Promise例項</title>
    <style type="text/css"></style>
    <script type="text/javascript">
        window.onload = () => {
            //建立例項promise1
            let promise1 = new Promise((resolve) => {
                setTimeout(() => {
                    resolve(`promise1操作成功`);
                    console.log(`1`)
                }, 3000);
            });

            //建立例項promise1
            let promise2 = new Promise((resolve) => {
                setTimeout(() => {
                    resolve(`promise1操作成功`);
                    console.log(`2`)
                }, 1000);
            });


            Promise.all([promise1, promise2]).then((result) => {
                console.log(result);
            });
        }
    </script>
</head>

<body>
    <div></div>
</body>

</html>

結果(注意看時間):

imgPromise.all()

程式碼說明:

1s後,promise2進入fulfilled狀態,間隔2s,也就是3s後,promise1也進入fulfilled狀態。這時,由於兩個例項都進入了fulfilled狀態,所以Promise.all()才進入了then方法。

使用場景:執行某個操作需要依賴多個介面請求回的資料,且這些介面之間不存在互相依賴的關係。這時使用Promise.all(),等到所有介面都請求成功了,它才會進行操作。

1.6 race()

和all()的引數一樣,引數中的promise例項,只要有一個狀態發生變化(不管是成功fulfilled還是異常rejected),它就會有返回,其他例項中再發生變化,它也不管了。

例項程式碼(可直接在瀏覽器中開啟):

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <title>Promise例項</title>
    <style type="text/css"></style>
    <script type="text/javascript">
        window.onload = () => {
            //建立例項promise1
            let promise1 = new Promise((resolve) => {
                setTimeout(() => {
                    resolve(`promise1操作成功`);
                    console.log(`1`)
                }, 3000);
            });

            //建立例項promise1
            let promise2 = new Promise((resolve, reject) => {
                setTimeout(() => {
                    reject(`promise1操作失敗`);
                    console.log(`2`)
                }, 1000);
            });


            Promise.race([promise1, promise2])
                .then((result) => {
                    console.log(result);
                })
                .catch((error) => {
                    console.log(error);
                })
        }
    </script>
</head>

<body>
    <div></div>
</body>

</html>

結果(注意看時間):

imgPromise.race()

程式碼說明:

1s後,promise2進入rejected狀態,由於一個例項的狀態發生了變化,所以Promise.race()就立刻執行了。

2 例項

平時開發中可能經常會遇到的問題是,要用ajax進行多次請求。例如現在有三個請求,請求A、請求B、請求C。請求C要將請求B的請求回來的資料做為引數,請求B要將請求A的請求回來的資料做為引數。

按照這個思路,我們可能會直接寫出這樣的層層巢狀的程式碼:

//------請求A 開始---------
    $.ajax({
        success:function(res1){


            //------請求B 開始----
            $.ajax({
                success:function(res2){


                    //----請求C 開始---
                    $.ajax({
                        success:function(res3){
                        }
                    });
                    //---請求C 結束---


                }    
            });
            //------請求B 結束-----


        }
    });
    //------請求A 結束---------

在請求A的success後,請求B傳送請求,在請求B 的success後,請求C傳送請求。請求C結束後,再向上到請求B結束,請求B結束後,再向上到請求A結束。

這樣雖然可以完成任務,但是程式碼層層巢狀,程式碼可讀性差,也不便於除錯和後續的程式碼維護。而如果用Promise,你可以這樣寫(示意程式碼,無ajax請求):

此處附上完整可執行程式碼,可在瀏覽器的控制檯中檢視執行結果:

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <title>Promise例項</title>
    <style type="text/css"></style>
    <script type="text/javascript">
        window.onload = () => {
            let promise = new Promise((resolve, reject) => {

                if (true) {
                    //呼叫操作成功方法
                    resolve(`操作成功`);
                } else {
                    //呼叫操作異常方法
                    reject(`操作異常`);
                }
            });

            //then處理操作成功,catch處理操作異常
            promise.then(requestA)
                .then(requestB)
                .then(requestC)
                .catch(requestError);

            function requestA() {
                console.log(`請求A成功`);
                return `下一個是請求B`;
            }
            function requestB(res) {
                console.log(`上一步的結果:` + res);
                console.log(`請求B成功`);
                return `下一個是請求C`;
            }
            function requestC(res) {
                console.log(`上一步的結果:` + res);
                console.log(`請求C成功`);
            }
            function requestError() {
                console.log(`請求失敗`);
            }
        }
    </script>
</head>

<body>
    <div></div>
</body>

</html>

結果如下:

img例項

可以看出請求C依賴請求B的結果,請求B依賴請求A的結果,在請求A中是使用了return將需要的資料返回,傳遞給下一個then()中的請求B,實現了引數的傳遞。同理,請求B中也是用了return,將引數傳遞給了請求C。

3.小結

本文主要介紹了Promise物件的三個狀態和兩個過程。“三個狀態”是:初始化、操作成功、操作異常,“兩個過程”是初始化狀態到操作成功狀態,和初始化狀態到操作異常狀態。除此之前,還有兩種例項方法:then()、catch()來繫結處理程式。類方法:Promise.all()、Promise.race()。如有問題,歡迎指正。

相關閱讀
【每日課程推薦】機器學習實戰!快速入門線上廣告業務及CTR相應知識

此文已由作者授權騰訊雲+社群釋出,更多原文請點選

搜尋關注公眾號「雲加社群」,第一時間獲取技術乾貨,關注後回覆1024 送你一份技術課程大禮包!

海量技術實踐經驗,盡在雲加社群

相關文章