【ES6】第十八節:教你如何使用ES6的Promise對象

轉自:微信公衆號—— web前端教程

 

        Promise對象,ES6新增的一個全新特性,今天我們要好好學習一下它。

 

Promise的設計初衷

        

        首先,我們先一起了解一下,爲什麼要設計出這麼一個玩意兒,用它是爲了解決什麼問題?

 

        帶着這個問題,我們來回想一下日常開發中,經常需要用到ajax請求數據,拿到數據後,再進行一些處理。

 

        可有一次,你需要用ajax進行多次請求,而且,每次請求都依賴上一次請求返回的數據來作爲參數,然後繼續發出請求,你把代碼寫成了這樣:

 

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


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


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


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


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

 

        上面的案例,假設請求C需要依賴請求B返回的數據,所以,C只能放在B的success函數內;B需要依賴A請求得到的數據作爲參數,所以,B只能放在A的success函數內;也就是:請求A包含着請求B,請求B又包含了請求C。

 

        就這樣,請求順序爲:請求A -> 請求B -> 請求C,最後你也能順利的完成了任務。

 

傳統寫法的不足

        

        但是這樣寫,會有一些缺點:

 

1.  如果存在多個請求操作層層依賴的話,那麼以上的嵌套就有可能不止三層那麼少了,加上每一層還會有複雜的業務邏輯處理,代碼可讀性也越來越差,不直觀,調試起來也不方便。如果多人開發的時候沒有足夠的溝通協商,大家的代碼風格不一致的話,更是雪上加霜,給後面的維護帶來極大的不便。

 

 

2.  如果請求C的需要依賴A和B的結果,但是請求A和B缺互相獨立,沒有依賴關係,以上的實現方式,就使得B必須得到A請求完成後纔可以執行,無疑是消耗了更多的等待時間

 

 

        既然使用這種回調函數層層嵌套(又稱:回調地獄)的形式存在缺點,ES6想了辦法治它,所以就有了Promise的出現了。

 

        那麼我們就知道了:Promise對象能使我們更合理、更規範地進行處理異步操作

 

 

Promise的基本用法 

        

        接下來,我們就看看它的基本用法:

 

    let pro = new Promise(function(resolve,reject){
        //....
    });

    

 

        Promise對象是全局對象,你也可以理解爲一個類,創建Promise實例的時候,要有那個new關鍵字。參數是一個匿名函數,其中有兩個參數:resolve和reject,兩個函數均爲方法。resolve方法用於處理異步操作成功後業務;reject方法用於操作異步操作失敗後的業務。

 

 

Promise的是三種狀態

        

Promise對象有三種狀態:

  1. pending:剛剛創建一個Promise實例的時候,表示初始狀態;

  2. fulfilled:resolve方法調用的時候,表示操作成功;

  3. rejected:reject方法調用的時候,表示操作失敗;

     

 

        狀態只能從 初始化 -> 成功  或者  初始化 -> 失敗,不能逆向轉換,也不能在成功fulfilled 和失敗rejected之間轉換

 

    let pro = new Promise(function(resolve,reject){
        //實例化後狀態:pending

        if('操作成功'){
            resolve();
            //resolve方法調用,狀態爲:fulfilled
        }else{
            reject();
            //reject方法調用,狀態爲:rejected
        }
    });

    

 

        上面的註釋,講清楚了一個Promise實例的狀態改變情況。

 

        初始化實例後,對象的狀態就變成了pending;當resolve方法被調用的時候,狀態就變成了:成功fulfilled;當reject方法被調用的時候,狀態就會有pending變成失敗rejected。

 

 

then( )方法

 

        瞭解了Promise的創建和狀態,我們來學習一個最重要的實例方法:then( )方法。

 

        then( )方法:用於綁定處理操作後的處理程序。

 

    

    pro.then(function (res) {
        //操作成功的處理程序
    },function (error) {
        //操作失敗的處理程序
    });

    

 

        參數是兩個函數,第一個用於處理操作成功後的業務,第二個用於處理操作異常後的業務

 

 

catch( )方法 

        

        對於操作異常的程序,Promise專門提供了一個實例方法來處理:catch( )方法。

 

    pro.catch(function (error) {
        //操作失敗的處理程序
    });

    

        

        catch只接受一個參數,用於處理操作異常後的業務。

 

        綜合上面的兩個方法,大家都建議將then方法用於處理操作成功,catch方法用於處理操作異常,也就是:

 

    pro.then(function (res) {
        //操作成功的處理程序
    }).catch(function (error) {
        //操作失敗的處理程序
    });

    

 

        之所以能夠使用鏈式調用,是因爲then方法和catch方法調用後,都會返回promise對象

 

        講了那麼多,如果你之前一點都沒接觸過Promise的話,現在一定很懵逼,沒關係,下面我們用一個案例來串聯前面的知識點,演示一下,認真閱讀註釋:

 

    //用new關鍵字創建一個Promise實例
    let pro = new Promise(function(resolve,reject){
        //假設condition的值爲true
        let condition = true;

        if(condition){
            //調用操作成功方法
            resolve('操作成功');
            //狀態:pending->fulfilled
        }else{
            //調用操作異常方法
            reject('操作異常');
            //狀態:pending->rejected
        }
    });

    //用then處理操作成功,catch處理操作異常
    pro.then(function (res) {

        //操作成功的處理程序
        console.log(res)

    }).catch(function (error) {

        //操作失敗的處理程序
        console.log(error)

    });
    //控制檯輸出:操作成功

 

        上面案例的註釋十分詳細,串聯起了上面介紹的所有知識點:創建實例,狀態轉換,then方法和catch方法的使用。

 

        由於我們設置了變量condition的值爲true,所以執行後控制檯輸出的結果是:“操作成功”。

 

        上面就是Promise用於處理操作異常的這個過程;但是,正如文章開頭講到的,如果多個操作之間層層依賴,我們用Promise又是怎麼處理的呢?

 

 

完整案例

        

        我們看看下面的案例,代碼有點長,但是一點都不復雜:

 

    let pro = new Promise(function(resolve,reject){

        if(true){
            //調用操作成功方法
            resolve('操作成功');
        }else{
            //調用操作異常方法
            reject('操作異常');
        }
    });

    //用then處理操作成功,catch處理操作異常
    pro.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('請求失敗');
    }

    //打印結果:
    //請求A成功
    //上一步的結果:請求B,下一個就是你了
    //請求B成功
    //上一步的結果:請求C,下一個就是你了
    //請求C成功

 

        案例中,先是創建一個實例,還聲明瞭4個函數,其中三個是分別代表着請求A,請求B,請求C;有了then方法,三個請求操作再也不用層層嵌套了。我們使用then方法,按照調用順序,很直觀地完成了三個操作的綁定,並且,如果請求B依賴於請求A的結果,那麼,可以在請求A的程序用使用return語句把需要的數據作爲參數,傳遞給下一個請求,案例中我們就是使用return實現傳遞參數給下一步操作的。

 

 

更直觀的圖解

 

        如果你還是是懂非懂,沒關係,前端君拼了,上圖:

 

 

 

        圖有點粗糙,但是能反應出上面程序的執行過程,幫助大家加深理解。

 

        除了提供了實例方法以外,Promise還提供了一些類方法,也就是不用創建實例,也可以調用的方法。

 

        下面,我們來學習幾個重要的。

 

 Promise.all( )方法

 

        Promise.all( )方法:接受一個數組作爲參數,數組的元素是Promise實例對象,當參數中的實例對象的狀態都爲fulfilled時,Promise.all( )纔會有返回

 

        我們來演示一下:

 

    //創建實例pro1
    let pro1 = new Promise(function(resolve){
        setTimeout(function () {
            resolve('實例1操作成功');
        },5000);
    });
    
    //創建實例pro2
    let pro2 = new Promise(function(resolve){
        setTimeout(function () {
            resolve('實例2操作成功');
        },1000);
    });

    
    Promise.all([pro1,pro2]).then(function(result){
        console.log(result);
    });
    //打印結果:["實例1操作成功", "實例2操作成功"]

 

        上述案例,我們創建了兩個Promise實例:pro1和pro2,我們注意兩個setTimeout的第二個參數,分別是5000毫秒和1000毫秒,當我們調用Promise.all( )方法的時候,會延遲到5秒才控制檯會輸出結果。

 

        因爲1000毫秒以後,實例pro2進入了成功fulfilled狀態;此時,Promise.all( )還不會有所行動,因爲實例pro1還沒有進入成功fulfilled狀態;等到了5000毫秒以後,實例pro1也進入了成功fulfilled狀態,Promise.all( )纔會進入then方法,然後在控制檯輸出:["實例1操作成功","實例2操作成功"]。

 

        這個方法有什麼用呢?一般這樣的場景:我們執行某個操作,這個操作需要得到需要多個接口請求回來的數據來支持,但是這些接口請求之前互不依賴,不需要層層嵌套。這種情況下就適合使用Promise.all( )方法,因爲它會得到所有接口都請求成功了,纔會進行操作。

 

 Promise.race( )方法

        

        另一個類似的方法是Promise.race()方法:它的參數要求跟Promise.all( )方法一樣,不同的是,它參數中的promise實例,只要有一個狀態發生變化(不管是成功fulfilled還是異常rejected),它就會有返回,其他實例中再發生變化,它也不管了。

 

    //初始化實例pro1
    let pro1 = new Promise(function(resolve){
        setTimeout(function () {
            resolve('實例1操作成功');
        },4000);
    });

    //初始化實例pro2
    let pro2 = new Promise(function(resolve,reject){
        setTimeout(function () {
            reject('實例2操作失敗');
        },2000);
    });

    Promise.race([pro2,pro1]).then(function(result){
        console.log(result);
    }).catch(function(error){
        console.log(error);
    });
    //打印結果:實例2操作失敗

 

        同樣是兩個實例,實例pro1不變,不同的是實例pro2,這次我們調用的是失敗函數reject。

 

        由於pro2實例中2000毫秒之後就執行reject方法,早於實例pro1的4000毫秒,所以最後輸出的是:實例2操作失敗。

 

        以上就是對Promise對象的內容講解,上面提到了一個概念:回調地獄;指的是過多地使用回調函數嵌套,使得調試和維護起來極其的不便。

 

        下面給大家看個真正的回調地獄,前方高能,圖慎點:

 

 

 

本節小結

 

 

總結:Promise是一個讓開發者更合理、更規範地用於處理異步操作的對象,它有三種狀態:初始化、操作成功、操作異常。使用實例方法:then( ) 和  catch( ) 來綁定處理程序;還提供了類方法:Promise.all( ) 和 Promise.race( )。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章