1. 程式人生 > >Promise專案實踐與異常處理方式

Promise專案實踐與異常處理方式

Promise是解決回撥地獄的好工具,比起直接使用回撥函式promise的語法結構更加清晰,程式碼的可讀性大大增加。但是想要在真是的專案中恰當的運用promise可不是隨便寫個Demo這個簡單的,如果運用不當反而會增加程式碼的複雜性。

1. 使用Promise經常遇到的問題

1.1 老舊瀏覽器沒有Promise全域性物件增麼辦?

如果辛辛苦苦寫完程式碼,測試後發現不相容IE6、7增麼辦?難道要推翻用回撥函式重寫?當然不是這樣,輪子早就造好了。

我們可以使用es6-promise-polyfill。es6-promise-polyfill可以使用頁面標籤直接引入,可以通過es6的import方法引入(如果你是用webpack),在node中可以使用require引入,也可以在Seajs中作為依賴引入。

引入這個polyfill之後,它會在window物件中加入Promise物件。這樣我們就可以全域性使用Promise了。

es6-promise-polyfill 下載地址

1.2 為什麼我寫的promise看起來和回撥地獄差不多?

我曾經看到過有人這樣寫promise,這等同於回撥地獄。

var wrongPromiseOuter =
    new Promise(function (resolve) {
        // 注意到了嗎?這裡在一個promise的內部建立了另一個promise
        setTimeout(function () {
            // 這裡在promise的回撥函式中又增加了一個回撥函式 TT
var wrongPromiseInner = new Promise(function (resolve) { setTimeout(function () { resolve('innerResponse'); }, 1000); }) wrongPromiseInner.then(Response => { resolve('outerResponse'); }) }, 1000
); }) wrongPromiseOuter.then(Response => { console.log(Response); })

正確的做法是

var rightPromiseOuter =
    new Promise(function (resolve) {
        setTimeout(function () {
            resolve('outerResponse');
        }, 1000);
    });

rightPromiseOuter
.then(Response => {
    console.log(Response);
    return new Promise(function (resolve) {
                setTimeout(function () {
                    resolve('innerResponse');
                }, 1000);
            })
})
.then(Response => {
    console.log(Response);
})    

1.3 什麼樣的業務邏輯適合使用

如果業務邏輯中存在大量的條件判斷,那這樣的業務並不適合promise。Promise適合業務流程單一,不存在大量分支判斷的情況。我們舉例說明為什麼過多的分支判斷不適合promise。

這裡我舉了一個極端的例子,它會導致多個判斷分支,使得每一次promise.then中的response會根據輸入的引數而變化,這樣我們處理response時就需要判斷多種情況,程式碼就不易管理了。

function start(arg) {
    /* 
    這裡是promise的開端 
    第一個非同步造作會根據引數arg來選擇執行不同的非同步操作,
    並返回不同的結果
    */
    if (arg === 'conditionOne') {
        Promise.resolve('c1');
    }
    if (arg === 'conditionTwo') {
        Promise.resolve('c2');
    }
}

start('conditionOne')
.then(response => {
    /* 
    這是第二次非同步操作,這裡根據第二個判斷條件otherCondition繼續進入不同的非同步操作 
    至此promise鏈中已經有了4條不同的分支
    */
    var otherCondition = 'ot1';

    if (response === 'c1') {
        if (otherCondition === 'ot1') {
            Promise.resolve('c11');
        } else {
            Promise.resolve('c12');
        }
    }

    if (response === 'c2') {
        if (otherCondition === 'ot1') {
            Promise.resolve('c21');
        } else {
            Promise.resolve('c21');
        }
    }
})
.then(response => {
    /* 越來越多的條件判斷 */
})

針對這種情況有兩種解決方案,思路一樣的。一、我們可以建立多個promise呼叫鏈而不是把所有的情況寫在同一個呼叫鏈中。二、可以用造好的輪子promise-conditional

1.4 如何進行異常處理?

參照promise的文件我們可以在reject回撥和catch中處理異常。但是promise規定如果一個錯誤在reject函式中被處理,那麼promise將從異常常態中恢復過來。這意味著接下來的then方法將接收到一個resolve回撥。大多數時候我們希望發生錯誤的時候,promise處理當前的異常並中斷後續的then操作。
我們先來看一個使用reject處理異常的例子

var promiseStart = new Promise(function(resolve, reject){
    reject('promise is rejected');
});

promiseStart
.then(function(response) {
    console.log('resolved');
    return new Promise(function(resolve, reject){
        resolve('promise is resolved');
    });
},function (error){
    console.log('rejected:', error);
    // 如果這裡不丟擲error,這個error將被吞掉,catch無法捕獲異常
    // 但是如果丟擲error,這個error會被下一個then的reject回撥處理,這不是我們想要的
    throw(error); 
})
.then(function (response){
    console.log('resolved:', response);
},function (error){
    console.log('rejected:', error);
    throw(error);
})
.catch(function(error) {
    console.log('catched:', error);
})

/* 
 輸出:
 rejected: promise is rejected
 rejected: promise is rejected
 catched: promise is rejected
 */

在這個例子中reject回撥處理了異常,但是它並不能中斷後續then操作。第二個then中的reject被觸發了。

而正確的做法是,不要使用reject!讓錯誤直接到catch中捕獲。

var promiseStart = new Promise(function(resolve, reject){
    reject('promise is rejected');
});

promiseStart
.then(function(response) {
    console.log('resolved');
    return new Promise(function(resolve, reject){
        resolve('promise is resolved');
    });
})
.then(function (response){
    console.log('resolved:', response);
})
.catch(function(error) {
    console.log('catched:', error);
})

/* 
 輸出:
 catched: promise is rejected
 */

這樣發生了錯誤後後續的then不會被呼叫,錯誤也只被catch處理一次。

嘻唰唰