碼迷,mamicode.com
首頁 > Web開發 > 詳細

JS - Promise使用隨筆

時間:2019-06-19 10:50:34      閱讀:74      評論:0      收藏:0      [點我收藏+]

標簽:success   并行執行   需要   指定   錯誤   rop   報錯   HERE   xxx   

一、promises相關概念

promises 的概念是由 CommonJS 小組的成員在 Promises/A 規范中提出來的。
 

1,then()方法介紹

根據 Promise/A 規范,promise 是一個對象,只需要 then 這一個方法。then 方法帶有如下三個參數:
  • 成功回調
  • 失敗回調
  • 前進回調(規范沒有要求包括前進回調的實現,但是很多都實現了)。
一個全新的 promise 對象從每個 then 的調用中返回。
 

2,Promise對象狀態

Promise 對象代表一個異步操作,其不受外界影響,有三種狀態:
  • Pending(進行中、未完成的)
  • Resolved(已完成,又稱 Fulfilled)
  • Rejected(已失敗)。
(1)promise 從未完成的狀態開始,如果成功它將會是完成態,如果失敗將會是失敗態。
(2)當一個 promise 移動到完成態,所有注冊到它的成功回調將被調用,而且會將成功的結果值傳給它。另外,任何注冊到 promise 的成功回調,將會在它已經完成以后立即被調用。
(3)同樣的,當一個 promise 移動到失敗態的時候,它調用的是失敗回調而不是成功回調。
(4)對包含前進特性的實現來說,promise 在它離開未完成狀態以前的任何時刻,都可以更新它的 progress。當 progress 被更新,所有的前進回調(progress callbacks)會被傳遞以 progress 的值,并被立即調用。前進回調被以不同于成功和失敗回調的方式處理;如果你在一個 progress 更新已經發生以后注冊了一個前進回調,新的前進回調只會在它被注冊以后被已更新的 progress 調用。
(5)注意:只有異步操作的結果,可以決定當前是哪一種狀態,任何其他操作都無法改變這個狀態。
 

4,目前支持Promises/A規范的庫

  • Q:可以在NodeJS 以及瀏覽器上工作,與jQuery兼容,可以通過消息傳遞遠程對象。
  • RSVP.js:一個輕量級的庫,它提供了組織異步代碼的工具。
  • when.js:體積小巧,使用方便。
  • NodeJS的Promise
  • jQuery 1.5:據說是基于“CommonJS Promises/A”規范
  • WinJS / Windows 8 / Metro

二、使用promises的優勢

1,解決回調地獄(Callback Hell)問題

(1)有時我們要進行一些相互間有依賴關系的異步操作,比如有多個請求,后一個的請求需要上一次請求的返回結果。過去常規做法只能 callback 層層嵌套,但嵌套層數過多的話就會有 callback hell 問題。比如下面代碼,可讀性和維護性都很差的。
1
2
3
4
5
6
7
8
9
10
11
12
firstAsync(function(data){
    //處理得到的 data 數據
    //....
    secondAsync(function(data2){
        //處理得到的 data2 數據
        //....
        thirdAsync(function(data3){
              //處理得到的 data3 數據
              //....
        });
    });
});
 

(2)如果使用 promises 的話,代碼就會變得扁平且更可讀了。前面提到 then 返回了一個 promise,因此我們可以將 then 的調用不停地串連起來。其中 then 返回的 promise 裝載了由調用返回的值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
firstAsync()
.then(function(data){
    //處理得到的 data 數據
    //....
    return secondAsync();
})
.then(function(data2){
    //處理得到的 data2 數據
    //....
    return thirdAsync();
})
.then(function(data3){
    //處理得到的 data3 數據
    //....
});

 

2,更好地進行錯誤捕獲 

多重嵌套 callback 除了會造成上面講的代碼縮進問題,更可怕的是可能會造成無法捕獲異常或異常捕獲不可控。
(1)比如下面代碼我們使用 setTimeout 模擬異步操作,在其中拋出了個異常。但由于異步回調中,回調函數的執行棧與原函數分離開,導致外部無法抓住異常。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function fetch(callback) {
    setTimeout(() => {
        throw Error(‘請求失敗‘)
    }, 2000)
}
 
try {
    fetch(() => {
        console.log(‘請求處理‘// 永遠不會執行
    })
catch (error) {
    console.log(‘觸發異常‘, error) // 永遠不會執行
}
 
// 程序崩潰
// Uncaught Error: 請求失敗
 
(2)如果使用 promises 的話,通過 reject 方法把 Promise 的狀態置為 rejected,這樣我們在 then 中就能捕捉到,然后執行“失敗”情況的回調。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
function fetch(callback) {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
             reject(‘請求失敗‘);
        }, 2000)
    })
}
 
 
fetch()
.then(
    function(data){
        console.log(‘請求處理‘);
        console.log(data);
    },
    function(reason, data){
        console.log(‘觸發異常‘);
        console.log(reason);
    }
);

當然我們在 catch 方法中處理 reject 回調也是可以的。
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
function fetch(callback) {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
             reject(‘請求失敗‘);
        }, 2000)
    })
}
 
 
fetch()
.then(
    function(data){
        console.log(‘請求處理‘);
        console.log(data);
    }
)
.catch(function(reason){
    console.log(‘觸發異常‘);
    console.log(reason);
});

第二部分:

JS - Promise使用詳解2(ES6中的Promise)

2015年6月, ES2015(即 ECMAScript 6、ES6) 正式發布。其中 Promise 被列為正式規范,成為 ES6 中最重要的特性之一。

 

1,then()方法

簡單來講,then 方法就是把原來的回調寫法分離出來,在異步操作執行完后,用鏈式調用的方式執行回調函數。
而 Promise 的優勢就在于這個鏈式調用。我們可以在 then 方法中繼續寫 Promise 對象并返回,然后繼續調用 then 來進行回調操作。
 
(1)下面通過樣例作為演示,我們定義做飯、吃飯、洗碗(cook、eat、wash)這三個方法,它們是層層依賴的關系,下一步的的操作需要使用上一部操作的結果。(這里使用 setTimeout 模擬異步操作)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
//做飯
function cook(){
    console.log(‘開始做飯。‘);
    var p = new Promise(function(resolve, reject){        //做一些異步操作
        setTimeout(function(){
            console.log(‘做飯完畢!‘);
            resolve(‘雞蛋炒飯‘);
        }, 1000);
    });
    return p;
}
 
//吃飯
function eat(data){
    console.log(‘開始吃飯:‘ + data);
    var p = new Promise(function(resolve, reject){        //做一些異步操作
        setTimeout(function(){
            console.log(‘吃飯完畢!‘);
            resolve(‘一塊碗和一雙筷子‘);
        }, 2000);
    });
    return p;
}
 
function wash(data){
    console.log(‘開始洗碗:‘ + data);
    var p = new Promise(function(resolve, reject){        //做一些異步操作
        setTimeout(function(){
            console.log(‘洗碗完畢!‘);
            resolve(‘干凈的碗筷‘);
        }, 2000);
    });
    return p;
}


(2)使用 then 鏈式調用這三個方法:

1
2
3
4
5
6
7
8
9
10
cook()
.then(function(data){
    return eat(data);
})
.then(function(data){
    return wash(data);
})
.then(function(data){
    console.log(data);
});


當然上面代碼還可以簡化成如下:

1
2
3
4
5
6
cook()
.then(eat)
.then(wash)
.then(function(data){
    console.log(data);
});


(3)運行結果如下:

技術圖片

2,reject()方法

上面樣例我們通過 resolve 方法把 Promise 的狀態置為完成態(Resolved),這時 then 方法就能捕捉到變化,并執行“成功”情況的回調。
而 reject 方法就是把 Promise 的狀態置為已失敗(Rejected),這時 then 方法執行“失敗”情況的回調(then 方法的第二參數)。
 
(1)下面同樣使用一個樣例做演示
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
//做飯
function cook(){
    console.log(‘開始做飯。‘);
    var p = new Promise(function(resolve, reject){        //做一些異步操作
        setTimeout(function(){
            console.log(‘做飯失敗!‘);
            reject(‘燒焦的米飯‘);
        }, 1000);
    });
    return p;
}
 
//吃飯
function eat(data){
    console.log(‘開始吃飯:‘ + data);
    var p = new Promise(function(resolve, reject){        //做一些異步操作
        setTimeout(function(){
            console.log(‘吃飯完畢!‘);
            resolve(‘一塊碗和一雙筷子‘);
        }, 2000);
    });
    return p;
}
 
cook()
.then(eat, function(data){
  console.log(data + ‘沒法吃!‘);
})

運行結果如下:

技術圖片
(2)如果我們只要處理失敗的情況可以使用 then(null, ...),或是使用接下來要講的 catch 方法。
1
2
3
4
cook()
.then(nullfunction(data){
  console.log(data + ‘沒法吃!‘);
})

 

3,catch()方法

(1)它可以和 then 的第二個參數一樣,用來指定 reject 的回調

1
2
3
4
5
cook()
.then(eat)
.catch(function(data){
    console.log(data + ‘沒法吃!‘);
});


(2)它的另一個作用是,當執行 resolve 的回調(也就是上面 then 中的第一個參數)時,如果拋出異常了(代碼出錯了),那么也不會報錯卡死 js,而是會進到這個 catch 方法中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
//做飯
function cook(){
    console.log(‘開始做飯。‘);
    var p = new Promise(function(resolve, reject){        //做一些異步操作
        setTimeout(function(){
            console.log(‘做飯完畢!‘);
            resolve(‘雞蛋炒飯‘);
        }, 1000);
    });
    return p;
}
 
//吃飯
function eat(data){
    console.log(‘開始吃飯:‘ + data);
    var p = new Promise(function(resolve, reject){        //做一些異步操作
        setTimeout(function(){
            console.log(‘吃飯完畢!‘);
            resolve(‘一塊碗和一雙筷子‘);
        }, 2000);
    });
    return p;
}
 
cook()
.then(function(data){
    throw new Error(‘米飯被打翻了!‘);
    eat(data);
})
.catch(function(data){
    console.log(data);
});

運行結果如下:

技術圖片
這種錯誤的捕獲是非常有用的,因為它能夠幫助我們在開發中識別代碼錯誤。比如,在一個 then() 方法內部的任意地方,我們做了一個 JSON.parse() 操作,如果 JSON 參數不合法那么它就會拋出一個同步錯誤。用回調的話該錯誤就會被吞噬掉,但是用 promises 我們可以輕松的在 catch() 方法里處理掉該錯誤。
 
(3)還可以添加多個 catch,實現更加精準的異常捕獲。
1
2
3
4
5
6
7
8
9
10
11
somePromise.then(function() {
 return a();
}).catch(TypeError, function(e) {
 //If a is defined, will end up here because
 //it is a type error to reference property of undefined
}).catch(ReferenceError, function(e) {
 //Will end up here if a wasn‘t defined at all
}).catch(function(e) {
 //Generic catch-the rest, error wasn‘t TypeError nor
 //ReferenceError
});
 

4,all()方法

Promise 的 all 方法提供了并行執行異步操作的能力,并且在所有異步操作執行完后才執行回調。
 
(1)比如下面代碼,兩個個異步操作是并行執行的,等到它們都執行完后才會進到 then 里面。同時 all 會把所有異步操作的結果放進一個數組中傳給 then。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
//切菜
function cutUp(){
    console.log(‘開始切菜。‘);
    var p = new Promise(function(resolve, reject){        //做一些異步操作
        setTimeout(function(){
            console.log(‘切菜完畢!‘);
            resolve(‘切好的菜‘);
        }, 1000);
    });
    return p;
}
 
//燒水
function boil(){
    console.log(‘開始燒水。‘);
    var p = new Promise(function(resolve, reject){        //做一些異步操作
        setTimeout(function(){
            console.log(‘燒水完畢!‘);
            resolve(‘燒好的水‘);
        }, 1000);
    });
    return p;
}
 
Promise
.all([cutUp(), boil()])
.then(function(results){
    console.log("準備工作完畢:");
    console.log(results);
});


(2)運行結果如下:

技術圖片

5,race()方法

race 按字面解釋,就是賽跑的意思。race 的用法與 all 一樣,只不過 all 是等所有異步操作都執行完畢后才執行 then 回調。而 race 的話只要有一個異步操作執行完畢,就立刻執行 then 回調。
注意:其它沒有執行完畢的異步操作仍然會繼續執行,而不是停止。
 
(1)這里我們將上面樣例的 all 改成 race
1
2
3
4
5
6
Promise
.race([cutUp(), boil()])
.then(function(results){
    console.log("準備工作完畢:");
    console.log(results);
});
技術圖片
 
(2)race 使用場景很多。比如我們可以用 race 給某個異步請求設置超時時間,并且在超時后執行相應的操作。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
//請求某個圖片資源
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 函數異步請求一張圖片,timeout 函數是一個延時 5 秒的異步操作。我們將它們一起放在 race 中賽跑。

  • 如果 5 秒內圖片請求成功那么便進入 then 方法,執行正常的流程。
  • 如果 5 秒鐘圖片還未成功返回,那么則進入 catch,報“圖片請求超時”的信息。
技術圖片

 

第三部分:

JS - Promise使用詳解3(jQuery中的Deferred)

上文我介紹了 ES6 中的 Promise,它完全遵循 Promises/A 規范。而我們熟悉的 jQuery 又有自己的 Promise 實現:Deferred(但其并不是遵循 Promises/A 規范)。本文就講講 jQuery 中 Promise 的實現。

 

一、Deferred對象及其方法

1,$.Deferred

  • jQuery 用 $.Deferred 實現了 Promise 規范。
  • $.Deferred() 返回一個對象,我們可以稱之為 Deferred 對象,上面掛著一些熟悉的方法如:done、fail、then 等。
  • jQuery 就是用這個 Deferred 對象來注冊異步操作的回調函數,修改并傳遞異步操作的狀態。
 

下面我們定義做飯、吃飯、洗碗(cook、eat、wash)這三個方法(這里使用 setTimeout 模擬異步操作)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
//做飯
function cook(){
    console.log(‘開始做飯。‘);
    var def = $.Deferred();
    //執行異步操作
    setTimeout(function(){
        console.log(‘做飯完畢!‘);
        def.resolve(‘雞蛋炒飯‘);
    }, 1000);
    return def.promise();
}
 
//吃飯
function eat(data){
    console.log(‘開始吃飯:‘ + data);
    var def = $.Deferred();
    //執行異步操作
    setTimeout(function(){
        console.log(‘吃飯完畢!‘);
        def.resolve(‘一塊碗和一雙筷子‘);
    }, 1000);
    return def.promise();
}
 
//洗碗
function wash(data){
    console.log(‘開始洗碗:‘ + data);
    var def = $.Deferred();
    //執行異步操作
    setTimeout(function(){
        console.log(‘洗碗完畢!‘);
        def.resolve(‘干凈的碗筷‘);
    }, 1000);
    return def.promise();
}

 

2,then()方法

通過 Deferred 對象的 then 方法我們可以實現鏈式調用。
(1)比如上面樣例的三個方法是層層依賴的關系,且下一步的的操作需要使用上一部操作的結果。我們可以這么寫:
1
2
3
4
5
6
7
8
9
10
cook()
.then(function(data){
    return eat(data);
})
.then(function(data){
    return wash(data);
})
.then(function(data){
    console.log(data);
});

當然也可以簡寫成如下:

1
2
3
4
5
6
cook()
.then(eat)
.then(wash)
.then(function(data){
    console.log(data);
});


(2)運行結果如下:

技術圖片

3,reject()方法

上面樣例我們通過 resolve 方法把 Deferred 對象的狀態置為完成態(Resolved),這時 then 方法就能捕捉到變化,并執行“成功”情況的回調。
而 reject 方法就是把 Deferred 對象的狀態置為已失敗(Rejected),這時 then 方法執行“失敗”情況的回調(then 方法的第二參數)。
 
(1)下面同樣使用一個樣例做演示
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
//做飯
function cook(){
    console.log(‘開始做飯。‘);
    var def = $.Deferred();
    //執行異步操作
    setTimeout(function(){
        console.log(‘做飯完畢!‘);
        def.reject(‘燒焦的米飯‘);
    }, 1000);
    return def.promise();
}
 
//吃飯
function eat(data){
    console.log(‘開始吃飯:‘ + data);
    var def = $.Deferred();
    //執行異步操作
    setTimeout(function(){
        console.log(‘吃飯完畢!‘);
        def.resolve(‘一塊碗和一雙筷子‘);
    }, 1000);
    return def.promise();
}
 
cook()
.then(eat, function(data){
  console.log(data + ‘沒法吃!‘);
})

運行結果如下:

技術圖片
(2)Promise 規范中,then 方法接受兩個參數,分別是執行完成和執行失敗的回調。而 jQuery 中進行了增強,還可以接受第三個參數,就是在 pending(進行中)狀態時的回調。
1
deferred.then( doneFilter [, failFilter ] [, progressFilter ] )

 

4,done()與fail()方法

done 和 fail 是 jQuery 增加的兩個語法糖方法。分別用來指定執行完成和執行失敗的回調。

比如下面兩段代碼是等價的:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
//then方法
d.then(function(){
    console.log(‘執行完成‘);
}, function(){
    console.log(‘執行失敗‘);
});
 
//done方法、fail方法
d.done(function(){
    console.log(‘執行完成‘);
})
.fail(function(){
    console.log(‘執行失敗‘);
});

 

5,always()方法

jQuery 的 Deferred 對象上還有一個 always 方法,不論執行完成還是執行失敗,always 都會執行,有點類似 ajax 中的 complete。

1
2
3
4
5
6
cook()
.then(eat)
.then(wash)
.always(function(){
  console.log(‘上班去!‘);
})

 

二、與Promises/A規范的差異

在開頭講到,目前 Promise 事實上的標準是社區提出的 Promises/A 規范,jQuery 的實現并不完全符合 Promises/A,主要表現在對錯誤的處理不同。

1,ES6中對錯誤的處理

下面代碼我們在回調函數中拋出一個錯誤,Promises/A 規定此時 Promise 實例的狀態變為 reject,同時該錯誤會被下一個 catch 方法指定的回調函數捕獲。
1
2
3
4
5
6
7
8
cook()
.then(function(data){
    throw new Error(‘米飯被打翻了!‘);
    eat(data);
})
.catch(function(data){
    console.log(data);
});

 

2,jQuery中對錯誤的處理

同樣我們在回調函數中拋出一個錯誤,jQuery 的 Deferred 對象此時不會改變狀態,亦不會觸發回調函數,該錯誤一般情況下會被 window.onerror 捕獲。換句話說,在 Deferred 對象中,總是必須使用 reject 方法來改變狀態。

1
2
3
4
5
6
7
8
9
10
cook()
.then(function(data){
    throw new Error(‘米飯被打翻了!‘);
    eat(data);
})
 
window.onerror = function(msg, url, line) {
    console.log("發生錯誤了:" + msg);
    return true//如果注釋掉該語句,瀏覽器中還是會有錯誤提示,反之則沒有。
}
技術圖片

 

三、$.when方法

jQuery 中,還有一個 $.when 方法。它與 ES6 中的 all 方法功能一樣,并行執行異步操作,在所有的異步操作執行完后才執行回調函數。當有兩個地方要注意:
  • $.when 并沒有定義在 $.Deferred 中,看名字就知道,$.when 它是一個單獨的方法。
  • $.when 與 ES6 的 all 的參數稍有區別,它接受的并不是數組,而是多個 Deferred 對象。
 
(1)比如下面代碼,兩個個異步操作是并行執行的,等到它們都執行完后才會進到 then 里面。同時 all 會把所有異步操作的結果傳給 then。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
//切菜
function cutUp(){
    console.log(‘開始切菜。‘);
    var def = $.Deferred();
    //執行異步操作
    setTimeout(function(){
        console.log(‘切菜完畢!‘);
        def.resolve(‘切好的菜‘);
    }, 1000);
    return def.promise();
}
 
//燒水
function boil(){
    console.log(‘開始燒水。‘);
    var def = $.Deferred();
    //執行異步操作
    setTimeout(function(){
        console.log(‘燒水完畢!‘);
        def.resolve(‘燒好的水‘);
    }, 1000);
    return def.promise();
}
 
$.when(cutUp(), boil())
.then(function(data1, data2){
    console.log("準備工作完畢:");
    console.log(data1, data2);
});

 

四、Ajax函數與Deferred的關系

jQuery 中我們常常會用到的 ajax, get, post 等 Ajax 函數,其實它們內部都已經實現了 Deferred。這些方法調用后會返回一個受限的 Deferred 對象。既然是 Deferred 對象,那么自然也有上面提到的所有特性。

 

1,then方法

比如我們通過鏈式調用,連續發送多個請求。
1
2
3
4
5
6
7
8
9
10
11
12
13
req1 = function(){
    return $.ajax(/*...*/);
}
req2 = function(){
    return $.ajax(/*...*/);
}
req3 = function(){
    return $.ajax(/*...*/);
}
 
req1().then(req2).then(req3).done(function(){
    console.log(‘請求發送完畢‘);
});

 

2,success、error與complete方法

success、error、complete是 ajax 提供的語法糖,功能與 Deferred 對象的 done、fail、always 一致。比如下面兩段代碼功能是一致的:

1
2
3
4
5
6
7
8
9
10
11
//使用success、error、complete
$.ajax(/*...*/)
.success(function(){/*...*/})
.error(function(){/*...*/})
.complete(function(){/*...*/})
 
//使用done、fail、always
$.ajax(/*...*/)
.done(function(){/*...*/})
.fai(function(){/*...*/})
.always(function(){/*...*/})
 
原文出自https://www.cnblogs.com/sweeeper/p/8442613.html

JS - Promise使用隨筆

標簽:success   并行執行   需要   指定   錯誤   rop   報錯   HERE   xxx   

原文地址:https://www.cnblogs.com/cx19950223/p/11049880.html

(0)
(0)
   
舉報
評論 一句話評論(0
登錄后才能評論!
? 2014 mamicode.com 版權所有 京ICP備13008772號-2
迷上了代碼!
公式规律下期单双