1. 簡介
ES6引入了一個全新的對象Promise,用于表示一個異步操作的最終狀態(tài)(完成或失?。约捌浞祷氐闹?。Promise最直接的好處就是鏈?zhǔn)秸{(diào)用,另外在錯誤捕獲上也很方便。用同步的寫法解決異步問題,代碼直觀,易于理解維護(hù),解決了回調(diào)地獄的問題。關(guān)于Promise的詳細(xì)講解和更多用例我會開專門文章討論。這里我們主要看一下Promise及其原型的屬性和方法。
2. Promise對象創(chuàng)建
Promise對象使用new構(gòu)造函數(shù)創(chuàng)建?;臼褂梅椒ㄈ缦拢?/p>
const promise = new Promise(function(resolve, reject) {
// ... some code
if (/* 異步操作成功 */){
resolve(value);
} else {
reject(error);
}
});
Promise構(gòu)造函數(shù)接受一個函數(shù)作為參數(shù),該函數(shù)的兩個參數(shù)分別是resolve和reject。它們是兩個函數(shù),由 JavaScript 引擎提供,不用自己部署。
then方法可以接受兩個回調(diào)函數(shù)作為參數(shù)。第一個回調(diào)函數(shù)是Promise對象的狀態(tài)變?yōu)閞esolved時調(diào)用,第二個回調(diào)函數(shù)是Promise對象的狀態(tài)變?yōu)閞ejected時調(diào)用。其中,第二個函數(shù)是可選的,不一定要提供。這兩個函數(shù)都接受Promise對象傳出的值作為參數(shù)。
var promise = new Promise(function(resolve, reject) {
setTimeout(resolve, 1000, 'foo');
});
promise.then(function(val){
console.log(val); // 1000ms后輸出 ‘foo’
})
3. Promise構(gòu)造函數(shù)的屬性與方法
我們用Object.getOwnPropertyNames()方法獲取Promise構(gòu)造函數(shù)的所有屬性與方法。
Object.getOwnPropertyNames(Promise);
// (7) ["length", "name", "prototype", "all", "race", "resolve", "reject"]
發(fā)現(xiàn)一共有7個屬性和方法。
3.1 Promise構(gòu)造函數(shù)的屬性
Promise.length
長度總為1 (構(gòu)造器參數(shù)的數(shù)目)
Promise.name
名稱為"Promise"
Promise.prototype
指向Promise構(gòu)造函數(shù)的原型,可以為所有 Promise 類型的對象添加屬性。
3.2 Promise構(gòu)造函數(shù)的方法
Promise.all(iterable)
這個方法返回一個新的promise對象,該promise對象在iterable參數(shù)對象里所有的promise對象都成功的時候才會觸發(fā)成功,一旦有任何一個iterable里面的promise對象失敗則立即觸發(fā)該promise對象的失敗。這個新的promise對象在觸發(fā)成功狀態(tài)以后,會把一個包含iterable里所有promise返回值的數(shù)組作為成功回調(diào)的返回值,順序跟iterable的順序保持一致;如果這個新的promise對象觸發(fā)了失敗狀態(tài),它會把iterable里第一個觸發(fā)失敗的promise對象的錯誤信息作為它的失敗錯誤信息。Promise.all方法常被用于處理多個promise對象的狀態(tài)集合。(可以參考jQuery.when方法---MDN Promise譯者注)
var promise1 = Promise.resolve(3);
var promise2 = 42;
var promise3 = new Promise(function(resolve, reject) {
setTimeout(resolve, 100, 'foo');
});
Promise.all([promise1, promise2, promise3]).then(function(values) {
console.log(values);
});
// expected output: Array [3, 42, "foo"]
當(dāng)然,當(dāng)參數(shù)不包含 Promise 時, 該方法返回完成(resolve),但這顯然沒有什么意義。
Promise.race(iterable)
當(dāng)iterable參數(shù)里的任意一個子promise被成功或失敗后,父promise馬上也會用子promise的成功返回值或失敗詳情作為參數(shù)調(diào)用父promise綁定的相應(yīng)句柄,并返回該promise對象。
var promise1 = new Promise(function(resolve, reject) {
setTimeout(resolve, 500, 'one');
});
var promise2 = new Promise(function(resolve, reject) {
setTimeout(resolve, 100, 'two');
});
Promise.race([promise1, promise2]).then(function(value) {
console.log(value);
// Both resolve, but promise2 is faster
});
// expected output: "two"
Promise.reject(reason)
返回一個狀態(tài)為失敗的Promise對象,并將給定的失敗信息reason(Promise被拒絕的原因)傳遞給對應(yīng)的處理方法。此處使用Error實例的reason對調(diào)試和選擇性錯誤捕捉很有幫助。
Promise.reject("Testing static reject").then(function(reason) {
// 未被調(diào)用
}, function(reason) {
console.log(reason); // "Testing static reject"
});
Promise.reject(new Error("fail")).then(function(error) {
// 未被調(diào)用
}, function(error) {
console.log(error); // 堆棧跟蹤
/* Error: fail
at <anonymous>:7:16 */
});
Promise.resolve(value)
返回一個狀態(tài)由給定value決定的Promise對象。如果該值是一個Promise對象,則直接返回該對象;如果該值是thenable(即,帶有then方法的對象),返回的Promise對象的最終狀態(tài)由then方法執(zhí)行決定;否則的話(該value為空,基本類型或者不帶then方法的對象),返回的Promise對象狀態(tài)為fulfilled,并且將該value傳遞給對應(yīng)的then方法。通常而言,如果你不知道一個值是否是Promise對象,使用Promise.resolve(value) 來返回一個Promise對象,這樣就能將該value以Promise對象形式使用。
用法如下:
Promise.resolve(promise);
直接返回該對象promisePromise.resolve(thenable);
返回一個最終狀態(tài)由then方法執(zhí)行決定的Promise對象Promise.resolve(value)
value為空,基本類型,或者不帶then方法的對象,返回狀態(tài)為fulfilled的Promise對象,并且將該value傳遞給對應(yīng)的then方法
基本用法示例:
var promise1 = Promise.resolve([1, 2, 3]);
promise1.then(function(value) {
console.log(value);
// expected output: Array [1, 2, 3]
});
4. Promise原型對象的屬性與方法
我們用Object.getOwnPropertyNames()方法獲取Promise原型對象的所有屬性與方法。
Object.getOwnPropertyNames(Promise.prototype);
// (4) ["constructor", "then", "catch", "finally"]
發(fā)現(xiàn)一共有4個屬性和方法。
4.1 Promise原型對象的屬性
Promiset.prototype.constructor
指向構(gòu)造函數(shù)Promise
4.2 Promise原型對象的方法
Promise.prototype.then(onFullfilled, onRejected)
它最多需要有兩個參數(shù):Promise 的接受(fulfillment)和拒絕(rejection)情況的回調(diào)函數(shù)。返回一個新的 Promise,該Promise將以回調(diào)的返回值來resolve。
語法:
p.then(onFulfilled, onRejected);
p.then(function(value) {
// fulfillment
}, function(reason) {
// rejection
});
參數(shù):
- onFulfilled
當(dāng)Promise變成接受狀態(tài)(fulfillment)時,該參數(shù)作為回調(diào)函數(shù)被調(diào)用。該函數(shù)有一個參數(shù),即接受的值(the fulfillment value)。 - onRejected
當(dāng)Promise變成拒絕狀態(tài)(rejection )時,該參數(shù)作為回調(diào)函數(shù)被調(diào)用。該函數(shù)有一個參數(shù),即拒絕的原因(the rejection reason)。
返回值:
then方法返回一個Promise。而它的行為與then中的回調(diào)函數(shù)的返回值有關(guān):
如果then中的回調(diào)函數(shù)返回一個值,那么then返回的Promise將會成為接受狀態(tài),并且將返回的值作為接受狀態(tài)的回調(diào)函數(shù)的參數(shù)值。
如果then中的回調(diào)函數(shù)拋出一個錯誤,那么then返回的Promise將會成為拒絕狀態(tài),并且將拋出的錯誤作為拒絕狀態(tài)的回調(diào)函數(shù)的參數(shù)值。
如果then中的回調(diào)函數(shù)返回一個已經(jīng)是接受狀態(tài)的Promise,那么then返回的Promise也會成為接受狀態(tài),并且將那個Promise的接受狀態(tài)的回調(diào)函數(shù)的參數(shù)值作為該被返回的Promise的接受狀態(tài)回調(diào)函數(shù)的參數(shù)值。
如果then中的回調(diào)函數(shù)返回一個已經(jīng)是拒絕狀態(tài)的Promise,那么then返回的Promise也會成為拒絕狀態(tài),并且將那個Promise的拒絕狀態(tài)的回調(diào)函數(shù)的參數(shù)值作為該被返回的Promise的拒絕狀態(tài)回調(diào)函數(shù)的參數(shù)值。
如果then中的回調(diào)函數(shù)返回一個未定狀態(tài)(pending)的Promise,那么then返回Promise的狀態(tài)也是未定的,并且它的終態(tài)與那個Promise的終態(tài)相同;同時,它變?yōu)榻K態(tài)時調(diào)用的回調(diào)函數(shù)參數(shù)與那個Promise變?yōu)榻K態(tài)時的回調(diào)函數(shù)的參數(shù)是相同的。
注意:
如果忽略針對某個狀態(tài)的回調(diào)函數(shù)參數(shù),或者提供非函數(shù) (nonfunction) 參數(shù),那么 then 方法將會丟失關(guān)于該狀態(tài)的回調(diào)函數(shù)信息,但是并不會產(chǎn)生錯誤。如果調(diào)用 then 的 Promise 的狀態(tài)(fulfillment 或 rejection)發(fā)生改變,但是 then 中并沒有關(guān)于這種狀態(tài)的回調(diào)函數(shù),那么 then 將創(chuàng)建一個沒有經(jīng)過回調(diào)函數(shù)處理的新 Promise 對象,這個新 Promise 只是簡單地接受調(diào)用這個 then 的原 Promise 的終態(tài)作為它的終態(tài)。
用法示例:
var promise1 = new Promise(function(resolve, reject) {
resolve('Success!');
});
promise1.then(function(value) {
console.log(value);
// expected output: "Success!"
});
Promise.catch(onRejected)
添加一個拒絕(rejection) 回調(diào)到當(dāng)前 promise, 返回一個新的promise。當(dāng)這個回調(diào)函數(shù)被調(diào)用,新 promise 將以它的返回值來resolve。它的行為與調(diào)用Promise.prototype.then(undefined, onRejected) 相同。
語法:
p.catch(onRejected);
p.catch(function(reason) {
// 拒絕
});
參數(shù):
- onRejected
當(dāng)Promise 被拒絕時,被調(diào)用的一個Function。該函數(shù)擁有一個參數(shù): - reason
拒絕的原因。
返回值:
一個Promise。
示例:
有三種常見的使用情況:
- 使用鏈?zhǔn)秸Z句的 catch方法:
var p1 = new Promise(function(resolve, reject) {
resolve('Success');
});
p1.then(function(value) {
console.log(value); // "成功!"
throw 'oh, no!';
}).catch(function(e) {
console.log(e); // "oh, no!"
}).then(function(){
console.log('after a catch the chain is restored');
}, function () {
console.log('Not fired due to the catch');
});
// 以下行為與上述相同
p1.then(function(value) {
console.log(value); // "成功!"
return Promise.reject('oh, no!');
}).catch(function(e) {
console.log(e); // "oh, no!"
}).then(function(){
console.log('after a catch the chain is restored');
}, function () {
console.log('Not fired due to the catch');
});
- 捕獲拋出的錯誤
// 拋出一個錯誤,大多數(shù)時候?qū)⒄{(diào)用catch方法
var p1 = new Promise(function(resolve, reject) {
throw 'Uh-oh!';
});
p1.catch(function(e) {
console.log(e); // "Uh-oh!"
});
// 在異步函數(shù)中拋出的錯誤不會被catch捕獲到
var p2 = new Promise(function(resolve, reject) {
setTimeout(function() {
throw 'Uncaught Exception!';
}, 1000);
});
p2.catch(function(e) {
console.log(e); // 不會執(zhí)行
});
// 在resolve()后面拋出的錯誤會被忽略
var p3 = new Promise(function(resolve, reject) {
resolve();
throw 'Silenced Exception!';
});
p3.catch(function(e) {
console.log(e); // 不會執(zhí)行
});
- 如果已決議
//創(chuàng)建一個新的 Promise ,且已決議
var p1 = Promise.resolve("calling next");
var p2 = p1.catch(function (reason) {
//這個方法永遠(yuǎn)不會調(diào)用
console.log("catch p1!");
console.log(reason);
});
p2.then(function (value) {
console.log("next promise's onFulfilled"); /* next promise's onFulfilled */
console.log(value); /* calling next */
}, function (reason) {
console.log("next promise's onRejected");
console.log(reason);
});
Promise.prototype.finally(onFinally)
添加一個事件處理回調(diào)于當(dāng)前promise對象,并且在原promise對象解析完畢后,返回一個新的promise對象?;卣{(diào)會在當(dāng)前promise運行完畢后被調(diào)用,無論當(dāng)前promise的狀態(tài)是完成(fulfilled)還是失敗(rejected)
注意:
finally() 雖然與 .then(onFinally, onFinally) 類似,它們不同的是:
- 調(diào)用內(nèi)聯(lián)函數(shù)時,不需要多次聲明該函數(shù)或為該函數(shù)創(chuàng)建一個變量保存它。
由于無法知道promise的最終狀態(tài),所以finally的回調(diào)函數(shù)中不接收任何參數(shù),它僅用于無論最終結(jié)果如何都要執(zhí)行的情況。 - 與Promise.resolve(2).then(() => {}, () => {}) (resolved的結(jié)果為undefined)不同,Promise.resolve(2).finally(() => {}) resolved的結(jié)果為 2。
- 同樣,Promise.reject(3).then(() => {}, () => {}) (resolved 的結(jié)果為undefined), Promise.reject(3).finally(() => {}) rejected 的結(jié)果為 3。
用法示例:
一個典型的用法,在發(fā)出請求時,頁面的loading效果開啟,然后不管返回的結(jié)果是完成(fulfilled)還是失敗(rejected),都會執(zhí)行onFinally將loading效果取消。
let isLoading = true;
fetch(myRequest).then(function(response) {
var contentType = response.headers.get("content-type");
if(contentType && contentType.includes("application/json")) {
return response.json();
}
throw new TypeError("Oops, we haven't got JSON!");
})
.then(function(json) { /* process your JSON further */ })
.catch(function(error) { console.log(error); })
.finally(function() { isLoading = false; });
- Promise實例對象的屬性與方法
我們用Object.getOwnPropertyNames()方法獲取Promise實例對象的所有屬性與方法。
var p = new Promise(function(resolve, reject) {
resolve('success');
})
Object.getOwnPropertyNames(p); // []
我們發(fā)現(xiàn)實例本身沒有綁定屬性與方法。