JavaScript ES6 Promise Object 物件
Promise 是一種非同步 (asynchronous) 編程的解決方案,所謂的 Promise,簡單來說它是一個等待非同步操作完成的物件,當事件完成時,Promise 根據操作結果是成功、或者失敗,做相對應的處理動作。
一個 Promise 物件 (只) 會處於下面三種狀態之一:
- pending - 初始狀態 (進行中)
- fulfilled - 事件已完成
- rejected - 事件已失敗
Promise 狀態的改變只有兩種可能:
- 從 pending 變成 fulfilled
- 從 pending 變成 rejected
而一但狀態改變就會固定,永遠不會再改變狀態了。
非同步最常見的例子像是 AJAX,傳統在處理非同步事件時會用一堆 nested callbacks,Promise 則是提供另外一種解決方案,讓你更直觀地控制非同步操作。
來看看建立一個 Promise 物件的語法:
const promise = new Promise(function (resolve, reject) {
// ...
if (異步操作成功) {
resolve(value);
} else {
reject(error);
}
});
Promise 構造函數 (constructor) 接受一個函數作為參數,這個函數會在建立 Promise 物件的同時立刻被執行,該函數有兩個參數分別是 resolve 函數和 reject 函數,resolve/reject 這兩個函數會由 JavaScript interpreter 自動傳入。
- resolve(value) 函數的用途是用來將 Promise 物件的狀態變為 fulfilled (已完成),在非同步操作成功時調用,你可以將非同步操作的結果當作參數一起傳入
- reject(error) 函數的用途是用來將 Promise 物件的狀態變為 rejected (已失敗),在非同步操作失敗時調用,你可以將非同步操作的錯誤當作參數一起傳入
此外,當 Promise constructor 的函數參數執行時,如果內部發生錯誤 throw error,Promise 物件的狀態會自動變成 rejected。
Promise.prototype.then(onFulfilled, onRejected)
Promise 物件生成後,可以用 then() 方法來綁定當 fulfilled 或 rejected 狀態時,分別要執行的函數。
promise.then(
function (value) {
// 當狀態是 fulfilled (成功) 時,執行這個函數
// value 是透過 resolve() 傳進來的參數
},
function (error) {
// 當狀態是 rejected (失敗) 時,執行這個函數
// error 是透過 reject() 傳進來的參數
}
);
then() 方法接受兩個函數作為參數:
- 第一個函數是當 Promise 狀態變為成功時會被調用
- 第二個函數是當 Promise 狀態變為失敗時會被調用,這個參數是選擇性的不一定需要
舉一個 Promise 實際使用的例子:
const promise = new Promise(function (resolve, reject) {
// 執行非同步的 setTimeout
setTimeout(function () {
// 250ms 過後,將 Promise 物件狀態改為成功
resolve('Success!');
}, 250);
});
promise.then(function (successMessage) {
// 當 Promise 物件狀態變成功後執行這個函數
console.log('Yay! ' + successMessage);
});
// 250ms 後你會看到輸出 "Yay! Success!"
Promise.prototype.catch(onRejected)
Promise 物件生成後,可以用 catch() 方法來綁定當 rejected 狀態時,要執行的函數。
catch() 的用途就像是 then(undefined, onRejected)。
const promise = new Promise(function (resolve, reject) {
throw 'Uh-oh!';
});
promise.catch(function (e) {
console.log(e);
});
// 顯示 "Uh-oh!"
Promise.prototype.finally(onFinally)
finally() 方法是在 ES2018 (ES9) 引入的,用於指定不管是 fulfilled 還是 rejected,最後都一定會執行的回呼函式。這常用於清理資源,例如關閉載入指示器 (loading indicator)。
fetch('https://api.example.com/data')
.then((response) => response.json())
.catch((error) => console.error(error))
.finally(() => {
// 無論成功或失敗都會執行
hideLoadingSpinner();
});
Chaining 串接
then() 和 catch() 方法執行後都會返回一個新的 Promise 物件,讓你可以使用 chaining 的語法。
而後面的 then() 會接收前一個 then() 的 return value 當作參數。
例如:
const hello = new Promise(function (resolve, reject) {
resolve('Hello');
});
hello
.then(function (str) {
return str + ' World';
})
.then(function (str) {
return str;
})
.then(function (str) {
console.log(str);
});
// 最後輸出 "Hello World"
Promise.all(iterable)
Promise.all() 函數用來將多個 Promise 物件包裝成一個 Promise 物件,他接受的參數可以是一個陣列,陣列中放不同的 Promise 物件。
而新的 Promise 物件的狀態會怎麼改變?
- 狀態變為 fulfilled: 如果它包含的所有 Promise 物件狀態都變為 fulfilled。而所有 Promise 物件個別的返回值,會被組成一個陣列傳進 all Promise 物件的 callback
- 狀態變為 rejected: 如果它包含的其中一個 Promise 物件狀態變為 rejected。而第一個被 reject 的值會被傳進回 all Promise 物件的 callback
const p1 = Promise.resolve(3);
const p2 = 1337;
const p3 = new Promise(function (resolve, reject) {
setTimeout(resolve, 100, 'foo');
});
Promise.all([p1, p2, p3]).then(function (values) {
console.log(values);
});
// 會顯示 [3, 1337, "foo"]
Promise.race(iterable)
Promise.race() 函數和 Promise.all() 一樣用來將多個 Promise 物件包裝成一個 Promise 物件。
不同的地方在於,只要它包含的所有 Promise 物件其中任何一個的狀態先改變,race Promise 物件的狀態就會跟著改變,率先改變狀態的 Promise 物件參數會直接傳給 race Promise 物件的 callback。
const p1 = new Promise(function (resolve, reject) {
setTimeout(resolve, 500, 'one');
});
const p2 = new Promise(function (resolve, reject) {
setTimeout(resolve, 100, 'two');
});
Promise.race([p1, p2]).then(function (value) {
console.log(value);
});
// 會顯示 "two",因為 p2 比較快被 resolve
Promise.allSettled(iterable)
Promise.allSettled() 是 ES2020 引入的方法。它會等待所有 Promise 都執行完畢(不論成功或失敗),並回傳一個包含每個結果物件的陣列。
每個結果物件會有一個 status 屬性(fulfilled 或 rejected)。如果是成功的,會有 value;如果是失敗的,會有 reason。
const p1 = Promise.resolve(42);
const p2 = Promise.reject('Error');
Promise.allSettled([p1, p2]).then((results) => {
/*
results = [
{ status: 'fulfilled', value: 42 },
{ status: 'rejected', reason: 'Error' }
]
*/
});
Promise.any(iterable)
Promise.any() 是 ES2021 引入的方法。只要其中一個 Promise 成功 (fulfilled),它就會立刻回傳該結果。如果所有 Promise 都失敗,它會回傳一個 AggregateError 錯誤。
這與 Promise.race() 不同,race 是看誰先「結束」(不論成敗),而 any 是看誰先「成功」。
const p1 = new Promise((resolve, reject) => setTimeout(reject, 100, 'Fail 1'));
const p2 = new Promise((resolve, reject) => setTimeout(resolve, 200, 'Success 2'));
Promise.any([p1, p2]).then((value) => {
console.log(value); // "Success 2"
});
Promise.resolve(value)
Promise.resolve() 函數用來將一個物件轉型為 Promise (如果它不是一個 Promise 物件),然後立刻 resolve 它。
Promise.resolve('Success').then(
function (value) {
console.log(value);
},
function (value) {
console.log('Fail');
}
);
// 輸出 "Success"
Promise.reject(reason)
Promise.reject() 函數用來將一個物件轉型為 Promise (如果它不是一個 Promise 物件),然後立刻 reject 它。
Promise.reject(new Error('Fail')).then(
function (error) {
console.log('Success');
},
function (error) {
console.log('Fail');
}
);
// 輸出 "Fail"