Có 5 phương thức tĩnh trong lớp Promise
. Chúng ta sẽ nhanh chóng tìm hiểu các trường hợp sử dụng của họ ở đây.
Nội dung chính
1. Promise.all
Hãy nói rằng chúng ta muốn nhiều lời hứa thực hiện song song và đợi cho đến khi tất cả chúng sẵn sàng.
Ví dụ: tải xuống song song một số URL và xử lý nội dung sau khi hoàn tất.
Đó là những gì Promise.all
dành cho.
Cú pháp là:
let promise = Promise.all([...promises...]);
Promise.all
nhận một loạt các lời hứa (về mặt kỹ thuật có thể là bất kỳ lần lặp nào, nhưng thường là một mảng) và trả về một lời hứa mới.
Lời hứa mới giải quyết khi tất cả các lời hứa được liệt kê được giải quyết và mảng kết quả của chúng trở thành kết quả.
Chẳng hạn, phần Promise.all
bên dưới lắng xuống sau 3 giây, và sau đó kết quả của nó 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 thành viên mảng kết quả giống như trong các lời hứa nguồn của nó. Mặc dù lời hứa đầu tiên mất nhiều thời gian nhất để giải quyết, nhưng nó vẫn là lần đầu tiên trong mảng kết quả.
Một mẹo phổ biến là ánh xạ một mảng dữ liệu công việc thành một mảng các lời hứa và sau đó bọc 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ư thế này:
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}`)
));
Một ví dụ lớn hơn với việc tìm nạp thông tin người dùng cho một mảng người dùng GitHub bằng tên của họ (chúng ta có thể tìm nạp một mảng hàng hóa bằng id của họ, logic giống hệt nhau):
/*
Cafedev.vn - Kênh thông tin IT hàng đầu Việt Nam
@author cafedevn
Contact: cafedevn@gmail.com
Fanpage: https://www.facebook.com/cafedevn
Instagram: https://instagram.com/cafedevn
Twitter: https://twitter.com/CafedeVn
Linkedin: https://www.linkedin.com/in/cafe-dev-407054199/
*/
let names = ['iliakan', 'remy', 'jeresig'];
let requests = names.map(name => fetch(`https://api.github.com/users/${name}`));
Promise.all(requests)
.then(responses => {
// all responses are resolved successfully
for(let response of responses) {
alert(`${response.url}: ${response.status}`); // shows 200 for every url
}
return responses;
})
// map array of responses into an array of response.json() to read their content
.then(responses => Promise.all(responses.map(r => r.json())))
// all JSON answers are parsed: "users" is the array of them
.then(users => users.forEach(user => alert(user.name)));
Nếu bất kỳ lời hứa nào bị từ chối, lời hứa được trả lại bằng cách Promise.all
từ chối ngay lập tức với 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!
Ở đây lời hứa thứ hai từ chối trong hai giây. Điều đó dẫn đến một sự từ chối ngay lập tức Promise.all
, vì vậy .catch
thực thi: lỗi từ chối trở thành kết quả của toàn bộ Promise.all
.
Trong trường hợp có lỗi, các lời hứa khác sẽ bị bỏ qua
Nếu một lời hứa từ chối, Promise.all
ngay lập tức từ chối, hoàn toàn quên đi những điều khác trong danh sách. Kết quả của họ bị bỏ qua.
Ví dụ: nếu có nhiều cuộc gọifetch
, như trong ví dụ trên và một cuộc gọi thất bại, những cuộc gọi khác vẫn sẽ tiếp tục thực hiện, nhưng Promise.all
sẽ không xem chúng nữa. Họ có thể sẽ giải quyết, nhưng kết quả của họ sẽ bị bỏ qua.
Promise.all
không có gì để hủy bỏ chúng, vì không có khái niệm nào về việc hủy bỏ điều đó. Trong một chương khác, chúng ta sẽ đề cập AbortController
đến điều đó có thể giúp với điều đó, nhưng đó không phải là một phần của lời hứa API.Promise.all(iterable)
cho phép các giá trị thường xuyên không hứa hẹn iterable
Thông thường, Promise.all(...)
chấp nhận một lần lặp (trong hầu hết các trường hợp là một mảng) của các lời hứa. Nhưng nếu bất kỳ đối tượng nào trong số đó không phải là một lời hứa, thì nó sẽ được chuyển đến mảng kết quả thế này.
Ví dụ, ở đây kết quả là [1, 2, 3]
:
Promise.all([
new Promise((resolve, reject) => {
setTimeout(() => resolve(1), 1000)
}),
2,
3
]).then(alert); // 1, 2, 3
Vì vậy, chúng ta có thể chuyển các giá trị sẵn sàng đến Promise.all
nơi thuận tiện.
2. Promise.allSettled
Promise.all
từ chối toàn bộ nếu bất kỳ lời hứa nào từ chối. Điều đó tốt cho tất cả hoặc không có gì trong trường hợp sau, khi chúng ta cần tất cả các kết quả thành công để tiến hành:
Promise.all([
fetch('/template.html'),
fetch('/style.css'),
fetch('/data.json')
]).then(render); // render method needs results of all fetches
Promise.allSettled
chỉ chờ đợi tất cả các lời hứa để giải quyết, bất kể kết quả. Mảng kết quả có:
{status:"fulfilled", value:result}
để trả lời thành công,{status:"rejected", reason:error}
cho các lỗi.
Ví dụ: chúng ta muốn lấy thông tin về nhiều người dùng. Ngay cả khi một yêu cầu thất bại, chúng tôi vẫn quan tâm đến những yêu cầu khác.
Hãy 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}`);
}
});
});
Các results
trong dòng (*)
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 lời hứa, chúng ta nhận được trạng thái của nó và value/error
.
2.1. Polyfill
Nếu trình duyệt không hỗ trợ Promise.allSettled
, thật dễ dàng để polyfill:
if(!Promise.allSettled) {
Promise.allSettled = function(promises) {
return Promise.all(promises.map(p => Promise.resolve(p).then(value => ({
state: 'fulfilled',
value
}), reason => ({
state: 'rejected',
reason
}))));
};
}
Trong code này, promises.map
lấy các giá trị đầu vào, biến chúng thành các lời hứa (chỉ trong trường hợp một lời hứa không được thông qua) với p => Promise.resolve(p)
, và sau đó thêm .then
trình xử lý cho mọi người.
Trình xử lý đó biến một kết quả thành công value
thành {state:'fulfilled', value}
và một lỗi reason
thành {state:'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
để có được kết quả của tất cả các lời hứa đã cho, ngay cả khi một số trong số chúng từ chối.
3. Promise.race
Tương tự như vậy Promise.all
, nhưng chỉ chờ đợi cho lời hứa đã được giải quyết đầu tiên và nhận được kết quả (hoặc lỗi).
Cú pháp là:
let promise = Promise.race(iterable);
Chẳng hạn, ở đâ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
Lời hứa đầu tiên ở đây là nhanh nhất, vì vậy nó đã trở thành kết quả. Sau khi lời hứa đã được giải quyết đầu tiên, người thắng cuộc đua, tất cả các kết quả / lỗi tiếp theo sẽ bị bỏ qua.
4. Promise.resolve / reject
Các phương thức Promise.resolve
và Promise.reject
hiếm khi cần thiết trong code hiện đại, bởi vì async/await
cú pháp khiến chúng hơi lỗi thời.
Chúng ta bảo vệ chúng ở đây cho đầy đủ và cho những người không thể sử dụng async/await
vì một số lý do.
4.1. Promise.resolve
Promise.resolve(value)
tạo ra một lời hứa được giải quyết với kết quả value
.
Giống như:
let promise = new Promise(resolve => resolve(value));
Phương thức này được sử dụng để tương thích, khi một hàm được mong đợi sẽ trả lại một lời hứa.
Ví dụ: loadCached
hàm bên dưới tìm nạp một URL và ghi nhớ (lưu trữ) nội dung của nó. Đối với các cuộc gọi trong tương lai có cùng một URL, nó ngay lập tức nhận được nội dung trước đó từ bộ đệm, nhưng sử dụng Promise.resolve
để thực hiện lời hứa về nó, vì vậy giá trị được trả về luôn là một lời hứa:
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;
});
}
Chúng ta có thể viết loadCached(url).then(…)
, bởi vì chức năng được đảm bảo để trả lại một lời hứa. Chúng ta luôn có thể sử dụng .then
sau loadCached
. Đó là mục đích của Promise.resolve
dòng (*)
.
4.2. Promise.reject
Promise.reject(error)
tạo ra một lời hứa bị từ chối với error
.
Giống như:
let promise = new Promise((resolve, reject) => reject(error));
Trong thực tế, phương pháp này gần như không bao giờ được sử dụng.
5. Tóm lược
Có 5 phương thức tĩnh của Promise
lớp:
Promise.all(promises)
– chờ đợi tất cả các lời hứa sẽ giải quyết và trả về một mảng kết quả của chúng. Nếu bất kỳ lời hứa nào được từ chối, nó sẽ trở thành lỗiPromise.all
và tất cả các kết quả khác đều bị bỏ qua.Promise.allSettled(promises)
(phương thức được thêm gần đây) – chờ đợi tất cả các lời hứa sẽ giải quyết và trả về kết quả của chúng dưới dạng một mảng các đối tượng với:state
:"fulfilled"
hoặc"rejected"
value
(nếu hoàn thành) hoặcreason
(nếu bị từ chối).
Promise.race(promises)
– chờ đợi lời hứa đầu tiên được giải quyết và kết quả / lỗi của nó trở thành kết quả.Promise.resolve(value)
– thực hiện một lời hứa được giải quyết với giá trị nhất định.Promise.reject(error)
– thực hiện một lời hứa bị từ chối với lỗi đã cho.
Trong số năm, Promise.all
có lẽ là phổ biến nhất trong thực tế.
Full series tự học Javascript từ cơ bản tới nâng cao tại đây nha.
Nếu bạn thấy hay và hữu ích, bạn có thể tham gia các kênh sau của cafedev để nhận được nhiều hơn nữa:
Chào thân ái và quyết thắng!