1. 程式人生 > >ES6 Promise物件的用法

ES6 Promise物件的用法

ES6規定,Promise物件是一個建構函式,用來生成Promise例項

new 一個Promise物件

        var p = new Promise(function(resolve,reject){
            //做一些非同步操作
            setTimeout(function(){
                console.log('執行完成');
                resolve('成功輸出');
            },2000);
        });

Promise建構函式接受一個函式作為引數,該函式的兩個引數分別是resolve和reject。它們是兩個函式,有JavaScript引擎提供,不用自己部署

resolve函式的作用 : 將Promise物件的狀態從'未完成'變為'成功',在非同步操作成功時呼叫,並將非同步操作的結果作為引數傳遞出去

reject函式的作用:將Promise物件的狀態從'未完成'變為'失敗',在非同步操作失敗時呼叫,並將非同步操報出的錯誤作為引數傳遞出去

resolve是將Promise的狀態置為fullfiled,reject是將Promise的狀態置為rejected

在上面的程式碼中,我們執行了一個非同步操作,也就是setTimeout,2秒後,輸出“執行完成”,並且呼叫resolve方法。

執行程式碼,會在2秒後輸出“執行完成”。但是我只是new了一個物件,並沒有呼叫它,我們傳進去的函式就已經執行了,這是需要注意的一個細節。所以我們用Promise的時候一般是包在一個函式中,在需要的時候去執行這個函式

        function timeout() {
            var p = new Promise(function(resolve,reject){
            //做一些非同步操作
            setTimeout(function(){
                console.log('執行完成');
                resolve('成功輸出');
            },2000);
        });
        return p
        }
        timeout()

所以包裝這麼一個函式有什麼用?resolve('成功輸出')是有什麼用?

觀察函式,我們發現 , 在我們包裝好的函式最後,會return出Promise物件,也就是說,執行這個函式我們得到了一個Promise物件。知道Promise物件上有then、catch方法吧?這就是強大之處了,看下面的程式碼:

       timeout().then(function(data){
            console.log(data); 
            //後面可以用傳過來的資料做些其他操作
        })

在timeout()的返回上直接呼叫then方法,then接收一個引數,是函式,並且會拿到我們在timeout中呼叫resolve時傳的的引數。執行這段程式碼,會在2秒後輸出“執行完成”,緊接著輸出“成功輸出”。

在這裡看來,then裡面的函式就和以前我們所理解的回撥函式一個意思,但是,如果有多層回撥呢?看下面程式碼

     setTimeout(function(){
       timeout(function(){
         setTimeout(function(){
           timeout(function(){
             setTimeout(function(){
               timeout();
               },2000);
             });
          }, 2000);
        });
      }, 2000);

以上程式碼就是傳說中的回撥地獄,如果有多層業務邏輯巢狀的話,不僅會使程式碼閱讀困難,而且後面維護起來也是難點

看看Promise怎麼用

        timeout().then(function(data){
            console.log(data);
            return timeout2();
            //後面可以用傳過來的資料做些其他操作
        }).then(function(data){
            console.log(data);
            return timeout3();
        }).then(function(data){
            console.log(data);
        })

這樣能夠按順序,每隔兩秒輸出每個非同步回撥中的內容,在timeout2中傳給resolve的資料,能在接下來的then方法中拿到。執行結果如下:

        function timeout() {
            var p = new Promise(function(resolve,reject){
            //做一些非同步操作
            setTimeout(function(){
                console.log('執行完成');
                resolve('成功輸出');
            },2000);
        });
        return p
        }
        function timeout2() {
            var p = new Promise(function(resolve,reject){
            //做一些非同步操作
            setTimeout(function(){
                console.log('執行完成2');
                resolve('成功輸出2');
            },2000);
        });
        return p
        }
        function timeout3() {
            var p = new Promise(function(resolve,reject){
            //做一些非同步操作
            setTimeout(function(){
                console.log('執行完成3');
                resolve('成功輸出3');
            },2000);
        });
        return p
        }

reject的用法

將Promise物件的狀態從'未完成'變為'成功',在非同步操作成功時呼叫,並將非同步操作的結果作為引數傳遞出去

舉個栗子:

    function getNum(){
        var p = new Promise(function(resolve,reject){
            setTimeout(function(){
                var num = Math.ceil(Math.random()*10); //生成1-10的隨機數
            if(num>5){
                resolve(num)
            }else{
                reject('數字不對哦')
            }
            },2000)
        })
        return p;
    }
    getNum().then(function(data){
        console.log('resolved');
        console.log(data)
    },function(data){
        console.log('rejected')
        console.log(data);

    })

getNum函式用來非同步獲取一個數字,2秒後執行完成,如果數字大於5,我們認為是“成功”了,呼叫resolve修改Promise的狀態。否則我們認為是“失敗”了,呼叫reject並傳遞一個引數,作為失敗的原因。

then方法可以接受兩個引數,第一個對應resolve的回撥,第二個對應reject的回撥。所以我們能夠分別拿到他們傳過來的資料。多次執行這段程式碼,會得到不同的狀態

catch的用法

catch方法是then(null,rejection)的別名,用於指定發生錯誤時的回撥函式,所以上面也可以這麼寫

    getNum().then(function(data){
        console.log('resolved');
        console.log(data)
    }).catch(function(data){
        console.log('rejected')
        console.log(data);
    })

執行結果是一樣。不過它還有另外一個作用:在執行resolve的回撥(也就是上面then中的第一個引數)時,如果丟擲異常了(程式碼出錯了),那麼並不會報錯卡死js,而是會進到這個catch方法中。請看下面的程式碼:

    getNum().then(function(data){
        console.log('resolved');
        console.log(data);
        console.log(sss);   //未定義
    }).catch(function(data){
        console.log('rejected')
        console.log(data);
    })

在resolve的回撥中,我們console.log(somedata);而somedata這個變數是沒有被定義的。如果我們不用Promise,程式碼執行到這裡就直接在控制檯報錯了

也就是說進到catch方法裡面去了,而且把錯誤原因傳到了reason引數中。即便是有錯誤的程式碼也不會報錯了,這與我們的try/catch語句有相同的功能。

all的用法

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

    Promise.all([timeout(),timeout2(),timeout3()]).then(function(results){
        console.log(results);
    })

用Promise.all來執行,all接收一個數組引數,裡面的值最終都算返回Promise物件。這樣,三個非同步操作的並行執行的,等到它們都執行完後才會進到then裡面。那麼,三個非同步操作返回的資料哪裡去了呢?都在then裡面呢,all會把所有非同步操作的結果放進一個數組中傳給then,就是上面的results。

race的用法

「誰跑的快,以誰為準執行回撥」,這就是race方法,race的用法與all一樣,我們把上面timeout2的延時改為1秒來看一下

    Promise.race([timeout(),timeout2(),timeout3()]).then(function(results){
        console.log(results);
    })

我們可以用race給某個非同步請求設定超時時間,並且在超時後執行相應的操作

//請求某個圖片資源
function requestImg(){
    var p = new Promise(function(resolve, reject){
        var img = new Image();
        img.onload = function(){
            resolve(img);
        }
        img.src = 'xxxxxx';
    });
    return p;
}

//延時函式,用於給請求計時
function timeout(){
    var p = new Promise(function(resolve, reject){
        setTimeout(function(){
            reject('圖片請求超時');
        }, 5000);
    });
    return p;
}

Promise
.race([requestImg(), timeout()])
.then(function(results){
    console.log(results);
})
.catch(function(reason){
    console.log(reason);
});

requestImg函式會非同步請求一張圖片,我把地址寫為"xxxxxx",所以肯定是無法成功請求到的。timeout函式是一個延時5秒的非同步操作。我們把這兩個返回Promise物件的函式放進race,於是他倆就會賽跑,如果5秒之內圖片請求成功了,那麼遍進入then方法,執行正常的流程。如果5秒鐘圖片還未成功返回,那麼timeout就跑贏了,則進入catch,報出“圖片請求超時”的資訊。執行結果如下: