Promise 學習筆記二 :: 第一次 Promise 就上手

Promise 學習筆記二 :: 第一次 Promise 就上手
Promise 學習筆記二 :: 第一次 Promise 就上手

上篇說明了Promise規範產生的原因以及介紹Promise如何以簡潔的方式來管理非同步操作及鏈式執行,比起傳統的回調函式更加方便的靈活使用。

本篇介紹Promise常用的函式以及使用範例,希望透過這些範例可以讓你們能夠更加瞭解熟悉使用Promise,開發出來的程式有更高的閱讀性及維護性。

下表爲本文將介紹的函式功能一覽表:

函式引入版本簡單描述
Promise.prototype.then()ES6對應resolve及reject的回呼函式,並返回一個新的Promise實現鏈式執行。
Promise.prototype.catch()ES6相當於是then(undefined, onRejected)的縮寫。
Promise.prototype.finally()ES2018相當於是then(onFinally, onFinally)的縮寫。
Promise.allES6接收可迭代物件,直到所有Promise已解決或其中一個拒絕才回傳Promise。
Promise.allSettledES2020接收可迭代物件,直到所有Promise已解決或拒絕才回傳Promise。
Promise.raceES6接收可迭代物件,第一個Promise已解決或拒絕才回傳Promise。
Promise.anyES2021接收可迭代物件,第一個Promise已解決或所有都拒絕或傳入可迭代物件才回傳Promise。
Promise.rejectES6相當於 new Promise((resolve, reject) => { resolve(); });
Promise.resolveES6相當於 new Promise((resolve, reject) => { reject(); });

接下來將用實際程式碼範例來介紹。

then()函式最多可以傳入兩個參數如下:

  1. onFulfilled (Optional)
  2. onRejected (Optional)

用於對應resolve, reject的回呼函式,且返回值是一個新的Promise用於實現鏈式執行。

若onFulfilled的參數型態不是function則會被替換成以下的函式。

(value) => {
	return value
}

上方的函式會將原先的傳入的值往下一個Promise傳遞(穿透)。

與上同理若onRejected的參數型態不是function則會被替換成以下的函式。

(reason) => {
	throw reason
}

上方的函式會將原先傳入的值拋出到下一個Promise(穿透)。

catch()函式在ES6中被引入,此函式主要用於處理Promise被拒絕時執行的函式。

catch()相當於是then(undefined, onRejected)的縮寫。

new Promise((resolve, reject) => {
	reject('nicklabs.cc');
})
	.then((value) => {
		console.log(`success: `, value);
	})
	.catch((reason) => {
		console.log(`error: `, reason);
	});
	
// 輸出 error: nicklabs.cc

因為在then中未傳入onRejected函式,所以會將錯誤訊息將穿透到下一個then中,若then有實作onRejected函式則後方加上catch也不會執行。

new Promise((resolve, reject) => {
	reject('nicklabs.cc');
})
	.then(
		(value) => {
			console.log(`success: `, value);
		},
		(reason) => {
			console.log(`error 1: `, reason);
		}
	)
	.catch((reason) => {
		console.log(`error 2: `, reason);
	});
	
// 輸出 error 1: nicklabs.cc

若使用鏈式方式處理則可以選擇在最後面加上catch即可,不需在一個then中都實作onRejected函式。

finally函式在ES2018中被引入,此函式在Promise(已解決或拒絕)後觸發。

finally()相當於是then(onFinally, onFinally)的縮寫。

new Promise((resolve, reject) => {
	resolve('nicklabs.cc');
})
	.then((value) => {
		console.log(`success: `, value);
	})
	.finally(() => {
		console.log(`finally`);
	});
	
// 輸出 success: nicklabs.cc
// 輸出 finally

這對於需要在Promise結束後執行某些操作時特別有用。

如透過API讀取資料時會先開啟Loading畫面直到response完成時才會關閉Loading畫面。

new Promise((resolve, reject) => {
	console.log(`開啟Loading`);
	resolve('nicklabs.cc');
})
	.then((value) => {
		console.log(`success: `, value);
	})
	.finally(() => {
		console.log(`關閉Loading`);
	});

// 輸出 開啟Loading
// 輸出 success: nicklabs.cc
// 輸出 關閉Loading

在Loading的情境下若沒有finally函式,則需要在onFulfilled及onRejected中都需要加入關閉Loading的程式,但使用了finally則可以避免書寫重複的程式碼。

Promise.all函式在ES6中被引入,接收一個可迭代物件並返回Promise,等待所有傳入的Promise都已解決並以陣列回傳結果,但若有任何一個拒絕則會直接返回Promise並附帶拒絕原因。

const p1 = new Promise((resolve, reject) => {
	setTimeout(() => {
		resolve('p1');
	}, 1000);
});
const p2 = Promise.resolve('p2');
const p3 = new Promise((resolve, reject) => {
	resolve('p3');
});
const p4 = 'p4';
Promise.all([p1, p2, p3, p4])
	.then((values) => {
		console.log(values);
	});

// 輸出 ['p1', 'p2', 'p3', 'p4']

若有一個拒絕則會回傳Promise並附帶拒絕原因。

const p1 = new Promise((resolve, reject) => {
	setTimeout(() => {
		resolve('p1');
	}, 1000);
});
const p2 = Promise.reject('p2');
const p3 = new Promise((resolve, reject) => {
	resolve('p3');
});
const p4 = 'p4';
Promise.all([p1, p2, p3, p4])
	.then((values) => {
		console.log(values);
	});

// 輸出 Promise {<rejected>: 'p2'}

適合在需要同時呼叫多個Promise時使用。

Promise.allSettled函式在ES2020中被引入,接收一個可迭代物件並返回Promise,與all函式類似但差別在於需要等Promise都解決後(無論是已解決還是拒絕),才返回結果並附上Promise狀態便於判斷。

const p1 = new Promise((resolve, reject) => {
	setTimeout(() => {
		resolve('p1');
	}, 1000);
});
const p2 = Promise.resolve('p2');
const p3 = new Promise((resolve, reject) => {
	resolve('p3');
});
const p4 = 'p4';
Promise.allSettled([p1, p2, p3, p4])
	.then((values) => {
		console.log(values);
	});

// 輸出
//[
//{status: 'fulfilled', value: 'p1'},
//{status: 'fulfilled', value: 'p2'},
//{status: 'fulfilled', value: 'p3'},
//{status: 'fulfilled', value: 'p4'}
//]

若其中Proimse返回拒絕則可以從status判斷出來。

const p1 = new Promise((resolve, reject) => {
	setTimeout(() => {
		resolve('p1');
	}, 1000);
});
const p2 = Promise.reject('p2');
const p3 = new Promise((resolve, reject) => {
	resolve('p3');
});
const p4 = 'p4';
Promise.allSettled([p1, p2, p3, p4])
	.then((values) => {
		console.log(values);
	});

// 輸出
//[
//{status: 'fulfilled', value: 'p1'},
//{status: 'rejected', value: 'p2'},
//{status: 'fulfilled', value: 'p3'},
//{status: 'fulfilled', value: 'p4'}
//]

Promise.race函式在ES6中被引入,接收一個可迭代物件並返回第一個已解決或拒絕的Promise結果。

以下例子在p1在一秒後觸發resolve即達成返回條件。

const p1 = new Promise((resolve, reject) => {
	setTimeout(() => {
		resolve('p1');
	}, 1000);
});
const p2 = new Promise((resolve, reject) => {
	setTimeout(() => {
		resolve('p2');
	}, 2000);
});
const p3 = new Promise((resolve, reject) => {
	setTimeout(() => {
		reject('p3');
	}, 3000);
});
const p4 = new Promise((resolve, reject) => {
	setTimeout(() => {
		reject('p4');
	}, 4000);
});
Promise.race([p1, p2, p3, p4])
	.then((value) => {
		console.log(value);
	});
	
// 輸出 p1

Promise.any函式在ES2021中被引入,接收一個可迭代物件並返回第一個已解決Promise的結果,若傳入的Promise都拒絕或是傳入空的可迭代物件,則會拋出AggregateError訊息。

以下例子p1建立Promise立即執行resolve達成返回條件。

const p1 = new Promise((resolve, reject) => {
	resolve('p1');
});
const p2 = new Promise((resolve, reject) => {
	setTimeout(() => {
		resolve('p2');
	}, 1000);
});
const p3 = new Promise((resolve, reject) => {
	reject('p3');
});

Promise.any([p1, p2, p3])
	.then((value) => {
		console.log(value);
	});
	
// 輸出 'p1'

以下例子兩個Promise皆觸發拒絕函式則拋出AggregateError訊息

const p4 = new Promise((resolve, reject) => {
	reject('p4');
});
const p5 = new Promise((resolve, reject) => {
	reject('p5');
});

Promise.any([p4, p5])
	.then((value) => {
		console.log(value);
	});
	
// 拋出 'AggregateError: All promises were rejected'

以下例子傳入空的可迭代物件則拋出AggregateError訊息

Promise.any([])
	.then((value) => {
		console.log(value);
	});
	
// 泡出 'AggregateError: All promises were rejected'

Promise.reject函式在ES6中被引入,此函式返回一個已拒絕的Promise。

Promise.reject('error');

相當於以下程式碼:

new Promise((resolve, reject) => {
	reject('error');
});

Promise.resolve函式在ES6中被引入,此函式返回一個解決的Promise。

Promise.resolve('success');

相當於以下程式碼:

new Promise((resolve, reject) => {
	resolve('success');
});

 

作者頭像
Nick

是一位專業的網站開發者,不止擅長前端技術,也精通後端技術。