Promise API Trong JavaScript
Có lẽ tiêu đề bài viết đã phần nào lột tả được hết nội dung chính của bài viết này, đó chính là các phương thức trong lớp Promise
. Không để các bạn chờ đợi lâu, chúng ta cùng nhau tìm hiểu về Promise API
thông qua 6 phương thức tĩnh (Static
).
1. Promises.all
Giả sử chúng ta muốn nhiều promise
được thực thi song song và xử lí khi tất cả chúng đã sẵn sàng. Ví dụ: tải xuống một số URL song song và xử lý nội dung khi chúng hoàn tất. Để làm được điều đó thì Promises.all
là một cái tên không thể không nhắc đến. Cú pháp của Promise.all
là:
let promise = Promise.all([...promises...]);
Promise.all
nhận một mảng các promise
và nó trả về một promise
mới. Promise
mới được giải quyết khi tất cả các Promise
được liệt kê trước đó đã được giải quyết và kết quả của các promise
trước đó trở thành kết quả của chính promise
mới.
Ví dụ: Promise.all
bên dưới settles
3 giây và sau đó nó cho ra kết quả là một mảng [1, 2, 3]
:
Promise.all([
new Promise(resolve => setTimeout(() => resolve(1), 3000)), // 1
new Promise(resolve => setTimeout(() => resolve(2), 2000)), // 2
new Promise(resolve => setTimeout(() => resolve(3), 1000)) // 3
]).then(alert); // 1,2,3 when promises are ready: each promise contributes an array member
Xin lưu ý rằng thứ tự của các kết quả giống phải như trong các promise
gốc của nó. Mặc dù promises
đầu tiên mất nhiều thời gian nhất để giải quyết, nhưng nó vẫn là promise
đầu tiên trong mảng kết quả. Một thủ thuật phổ biến là ánh xạ một mảng dữ liệu thành một mảng các promise
, rồi gói nó vào Promise.all
. Ví dụ: nếu chúng ta có một loạt các URL, chúng ta có thể tìm nạp tất cả chúng như sau:
let urls = [
'https://api.github.com/users/iliakan',
'https://api.github.com/users/remy',
'https://api.github.com/users/jeresig'
];
// map every url to the promise of the fetch
let requests = urls.map(url => fetch(url));
// Promise.all waits until all jobs are resolved
Promise.all(requests)
.then(responses => responses.forEach(
response => alert(`${response.url}: ${response.status}`)
));
Lưu ý:
- Nếu bất kỳ
promise
nào bị từ chối,promise
được trả vềPromise.all
sẽ bị từ chối ngay lập tức với chính lỗi đó. Ví dụ:
Promise.all([
new Promise((resolve, reject) => setTimeout(() => resolve(1), 1000)),
new Promise((resolve, reject) => setTimeout(() => reject(new Error("Whoops!")), 2000)),
new Promise((resolve, reject) => setTimeout(() => resolve(3), 3000))
]).catch(alert); // Error: Whoops!
promises
thứ hai bị từ chối trong hai giây. Điều đó dẫn đến việc từ chối ngay lập tức Promise.all
, vì vậy sẽ .catch
sẽ thực thi: lỗi từ chối trở thành kết quả của toàn bộ Promise.all
.
Promise.all(iterable)
cho phép các giá trịnon-promise
"thông thường" trongiterable
Ví dụ đoạn code dưới đây cho kết quả là [1, 2, 3]
:
Promise.all([
new Promise((resolve, reject) => {
setTimeout(() => resolve(1), 1000)
}),
2,
3
]).then(alert); // 1, 2, 3
2. Promises.allSettled
Promise.allSettled
chỉ cần đợi cho tất cả các promise
giải quyết, bất kể kết quả. Mảng kết quả có:
{status:"fulfilled", value:result}
để có phản hồi thành công,{status:"rejected", reason:error}
cho các lỗi.
Ví dụ: chúng ta muốn tìm nạp thông tin về nhiều người dùng. Ngay cả khi một yêu cầu không thành công, chúng ta cũng có thể vẫn quan tâm đến những yêu cầu khác. Lúc này bạn nên sử dụng Promise.allSettled
:
let urls = [
'https://api.github.com/users/iliakan',
'https://api.github.com/users/remy',
'https://no-such-url'
];
Promise.allSettled(urls.map(url => fetch(url)))
.then(results => { // (*)
results.forEach((result, num) => {
if (result.status == "fulfilled") {
alert(`${urls[num]}: ${result.value.status}`);
}
if (result.status == "rejected") {
alert(`${urls[num]}: ${result.reason}`);
}
});
});
Dòng results
ở (*)
trên sẽ là:
[
{status: 'fulfilled', value: ...response...},
{status: 'fulfilled', value: ...response...},
{status: 'rejected', reason: ...error object...}
]
Vì vậy, đối với mỗi promise
, chúng ta có thể nhận được trạng thái của nó và value/error
.
Polyfill
Nếu trình duyệt không hỗ trợ Promise.allSettled
, bạn có thể dùng polyfill như sau:
if (!Promise.allSettled) {
const rejectHandler = reason => ({ status: 'rejected', reason });
const resolveHandler = value => ({ status: 'fulfilled', value });
Promise.allSettled = function (promises) {
const convertedPromises = promises.map(p => Promise.resolve(p).then(resolveHandler, rejectHandler));
return Promise.all(convertedPromises);
};
}
Giải thích 1 chút nè:
Trong đoạn code trên, promise.map
nhận các giá trị đầu vào, biến chúng thành các promise
(đề phòng trường hợp một promise
không được chuyển qua) với p => Promise.resolve(p)
, và sau đó thêm .then
cho mọi giá trị. Nếu thành công trình xử lý sẽ biến value
thành {status:'fulfilled', value}
và lỗi reason
thành {status:'rejected', reason}
. Đó chính xác là định dạng của Promise.allSettled
.
Bây giờ chúng ta có thể sử dụng Promise.allSettled
để nhận kết quả của tất cả các promise.
3. Promise.race
Tương tự như Promise.all
, nhưng nó chỉ đợi promise
đã giải quyết đầu tiên và nhận kết quả (hoặc lỗi) của nó. Cú pháp là:
let promise = Promise.race(iterable);
Ví dụ, ở đây kết quả sẽ là 1
Promise.race([
new Promise((resolve, reject) => setTimeout(() => resolve(1), 1000)),
new Promise((resolve, reject) => setTimeout(() => reject(new Error("Whoops!")), 2000)),
new Promise((resolve, reject) => setTimeout(() => resolve(3), 3000))
]).then(alert); // 1
promise
đầu tiên ở đây là nhanh nhất, vì vậy nó đã trở thành kết quả. Sau khi promise
đầu tiên "win", tất cả các kết quả / lỗi khác sẽ được bỏ qua.
4. Promise.any
Tương tự như Promise.race
, nhưng chỉ đợi promise
đầu tiên được thực thi và nhận được kết quả của nó. Nếu tất cả các promise
đã cho đều bị từ chối, thì promise
trả về sẽ bị từ chối với AggregateError
- một đối tượng đặc biệt lưu trữ tất cả các lỗi của promise
trong thuộc tính của nó errors
.
Cú pháp:
let promise = Promise.any(iterable);
Ví dụ dưới đây cho kết quả là 1
:
Promise.any([
new Promise((resolve, reject) => setTimeout(() => reject(new Error("Whoops!")), 1000)),
new Promise((resolve, reject) => setTimeout(() => resolve(1), 2000)),
new Promise((resolve, reject) => setTimeout(() => resolve(3), 3000))
]).then(alert); // 1
promise
đầu tiên ở đây là nhanh nhất, nhưng nó đã bị từ chối, vì vậy promise
thứ hai trở thành kết quả. Sau khi promise
đầu tiên "win", tất cả các kết quả tiếp theo đều bị bỏ qua.
Còn đây là một ví dụ khi tất cả các promise
đều thất bại:
Promise.any([
new Promise((resolve, reject) => setTimeout(() => reject(new Error("Ouch!")), 1000)),
new Promise((resolve, reject) => setTimeout(() => reject(new Error("Error!")), 2000))
]).catch(error => {
console.log(error.constructor.name); // AggregateError
console.log(error.errors[0]); // Error: Ouch!
console.log(error.errors[1]); // Error: Error
});
Như bạn có thể thấy, các lỗi của promise
đều có sẵn trong thuộc tính errors
của đối tượng AggregateError
.
5. Promise.resolve
Promise.resolve(value)
tạo ra một promise
đã được giải quyết với kết quả value
như sau:
let promise = new Promise(resolve => resolve(value));
Ví dụ: loadCached
hàm bên dưới tìm nạp một URL và ghi nhớ (lưu vào bộ nhớ đệm) nội dung của nó. Đối với các calls trong tương lai với cùng một URL, nó ngay lập tức lấy nội dung trước đó từ bộ nhớ cache, nhưng sử dụng Promise.resolve
để thực hiện promise
đó, vì vậy giá trị trả về luôn là một promise
:
let cache = new Map();
function loadCached(url) {
if (cache.has(url)) {
return Promise.resolve(cache.get(url)); // (*)
}
return fetch(url)
.then(response => response.text())
.then(text => {
cache.set(url,text);
return text;
});
}
6. Promise.reject
Promise.reject(error)
được tạo ra một promise
bị từ chối với error
giống như:
let promise = new Promise((resolve, reject) => reject(error));
Trong thực tế, các phương thức Promise.resolve
và Promise.reject
hiếm khi được sử dụng, bởi vì async/await
.Tuy nhiên bài viết này vẫn đề cập đến chúng ở đây để hoàn thiện và cho những bạn không thể sử dụng async/await
vì lý do nào đó.
Bài viết thứ 5 trong series tìm hiểu về promise, async/await
đã khép lại. Hẹn gặp lại các bạn trong bài 6 với chủ để Promisification