一、Promise對象
1.特點
Promise對象代表一個異步操作,有三種狀態:Pending(進行中)、Resolved(已完成,又稱Fulfilled)和Rejected(已失敗)。
一旦狀態改變,就不會再變,任何時候都可以得到這個結果。
有了Promise對象,就可以將異步操作以同步操作的流程表達出來,避免了層層嵌套的回調函數。
2.基本用法
var promise=new Promise(function(resolve,reject){
//...
if(/*異步操作成功*/){
resolve(value);
}else{
reject(value);
}
});
//調用
promise.then(function(value) {
// success
}, function(error) {
// failure
});
resolve:將Promise對象的狀態從“未完成”變爲“成功”(即從Pending變爲Resolved),在異步操作成功時調用,並將異步操作的結果,作爲參數傳遞出去。
reject:將Promise對象的狀態從“未完成”變爲“失敗”(即從Pending變爲Rejected),在異步操作失敗時調用,並將異步操作報出的錯誤,作爲參數傳遞出去。
3.API
1)then()
它的作用是爲Promise實例添加狀態改變時的回調函數。
then方法返回的是一個新的Promise實例(注意,不是原來那個Promise實例)。因此可以採用鏈式寫法,即then方法後面再調用另一個then方法。
示例:
getJSON("/post/1.json").then(
post => getJSON(post.commentURL)
).then(
comments => console.log("Resolved: ", comments),
err => console.log("Rejected: ", err)
);
2)catch()
catch方法是then(null, rejection)的別名,用於指定發生錯誤時的回調函數。
下面代碼中:
- getJSON方法返回一個Promise對象,如果該對象狀態變爲Resolved,則會調用then方法指定的回調函數;
- 如果異步操作拋出錯誤,狀態就會變爲Rejected,就會調用catch方法指定的回調函數,處理這個錯誤。
- 另外,then方法指定的回調函數,如果運行中拋出錯誤,也會被catch方法捕獲。
getJSON("/posts.json").then((posts)=>{
// ...
}).catch(// 處理 getJSON 和 前一個回調函數運行時發生的錯誤
error=>console.log('發生錯誤!', error);
);
如果Promise狀態已經變成Resolved,再拋出錯誤是無效的。
Promise對象的錯誤具有“冒泡”性質,會一直向後傳遞,直到被捕獲爲止。也就是說,錯誤總是會被下一個catch語句捕獲。
getJSON("/post/1.json").then(function(post) {
return getJSON(post.commentURL);
}).then(function(comments) {
// some code
}).catch(function(error) {
// 處理前面三個Promise產生的錯誤
});
一般來說,不要在then方法裏面定義Reject狀態的回調函數(即then的第二個參數),總是使用catch方法。
跟傳統的try/catch代碼塊不同的是,如果沒有使用catch方法指定錯誤處理的回調函數,Promise對象拋出的錯誤不會傳遞到外層代碼,即不會有任何反應。
catch方法返回的還是一個Promise對象,因此後面還可以接着調用then方法。
下面代碼中,第二個catch方法用來捕獲,前一個catch方法拋出的錯誤。
someAsyncThing().then(
()=>someOtherAsyncThing();
).catch(function(error) {
console.log('oh no', error);
// 下面一行會報錯,因爲y沒有聲明
y + 2;
}).catch(
error=>console.log('carry on', error);
);
// oh no [ReferenceError: x is not defined]
// carry on [ReferenceError: y is not defined]
3)Promise.all()
Promise.all方法用於將多個Promise實例,包裝成一個新的Promise實例。
Promise.all方法接受一個數組作爲參數,p1、p2、p3都是Promise對象的實例,如果不是,就會先調用下面講到的Promise.resolve方法,將參數轉爲Promise實例,再進一步處理。(Promise.all方法的參數可以不是數組,但必須具有Iterator接口,且返回的每個成員都是Promise實例。)
var p = Promise.all([p1, p2, p3]);
p的狀態由p1、p2、p3決定,分成兩種情況:
- 只有p1、p2、p3的狀態都變成fulfilled,p的狀態纔會變成fulfilled,此時p1、p2、p3的返回值組成一個數組,傳遞給p的回調函數。
- 只要p1、p2、p3之中有一個被rejected,p的狀態就變成rejected,此時第一個被reject的實例的返回值,會傳遞給p的回調函數。
4)Promise.race()
同樣是將多個Promise實例,包裝成一個新的Promise實例。
var p = Promise.race([p1, p2, p3]);
上面代碼中,只要p1、p2、p3之中有一個實例率先改變狀態,p的狀態就跟着改變。那個率先改變的 Promise 實例的返回值,就傳遞給p的回調函數。
5)Promise.resolve()
有時需要將現有對象轉爲Promise對象,Promise.resolve方法就起到這個作用。
var jsPromise = Promise.resolve($.ajax('/whatever.json'));
Promise.resolve方法的參數分成四種情況:
- 參數是一個Promise實例:那麼Promise.resolve將不做任何修改、原封不動地返回這個實例。
- 參數是一個thenable對象:thenable對象指的是具有then方法的對象。會將這個對象轉爲Promise對象,然後就立即執行thenable對象的then方法。
- 參數不是具有then方法的對象,或根本就不是對象:則Promise.resolve方法返回一個新的Promise對象,狀態爲Resolved。
- 不帶有任何參數:直接返回一個Resolved狀態的Promise對象。如果希望得到一個Promise對象,比較方便的方法就是直接調用Promise.resolve方法。
6)Promise.reject()
返回一個新的Promise實例,該實例的狀態爲rejected。它的參數用法與Promise.resolve方法完全一致。
7)Promise.try()
讓同步函數同步執行,異步函數異步執行,並且讓它們具有統一的 API 。
const f = () => console.log('now');
Promise.try(f);
console.log('next');
// now
// next
由於Promise.try爲所有操作提供了統一的處理機制,所以如果想用then方法管理流程,最好都用Promise.try包裝一下。可以更好地管理異常。
事實上,Promise.try就是模擬try代碼塊,就像promise.catch模擬的是catch代碼塊。
8)附加方法:done()
Promise對象的回調鏈,不管以then方法或catch方法結尾,要是最後一個方法拋出錯誤,都有可能無法捕捉到(因爲Promise內部的錯誤不會冒泡到全局)。因此,我們可以提供一個done方法,總是處於回調鏈的尾端,保證拋出任何可能出現的錯誤。
asyncFunc()
.then(f1)
.catch(r1)
.then(f2)
.done();
實現代碼:
Promise.prototype.done = function (onFulfilled, onRejected) {
this.then(onFulfilled, onRejected)
.catch(function (reason) {
// 拋出一個全局錯誤
setTimeout(() => { throw reason }, 0);
});
};
9)附加方法:finally()
用於指定不管Promise對象最後狀態如何,都會執行的操作。與done方法的最大區別,它接受一個普通的回調函數作爲參數,該函數不管怎樣都必須執行。
示例:
server.listen(0)
.then(function () {
// run test
})
.finally(server.stop);
實現代碼:
Promise.prototype.finally = function (callback) {
let P = this.constructor;
return this.then(
value => P.resolve(callback()).then(() => value),
reason => P.resolve(callback()).then(() => { throw reason })
);
};
4.應用
1)加載圖片
將圖片的加載寫成一個Promise,一旦加載完成,Promise的狀態就發生變化。
const preloadImage = function (path) {
return new Promise(function (resolve, reject) {
var image = new Image();
image.onload = resolve;
image.onerror = reject;
image.src = path;
});
};
2)Generator函數與Promise的結合
使用Generator函數管理流程,遇到異步操作的時候,通常返回一個Promise對象。