Promises trong JavaScript là một trong những API mạnh mẽ giúp chúng ta thực hiện các thao tác Async.
Promise.all đưa các thao tác Async lên một tầm cao mới vì nó giúp bạn tổng hợp một nhóm các lời hứa.
Nói cách khác, tôi có thể nói rằng nó giúp bạn thực hiện các thao tác đồng thời (đôi khi miễn phí).
điều kiện tiên quyết:
Bạn phải biết thế nào là một Lời hứa trong JavaScript.
Promise.all là gì?
Promise.all thực sự là một lời hứa lấy một loạt các lời hứa làm đầu vào (có thể lặp lại). Sau đó, nó sẽ được giải quyết khi tất cả các lời hứa được giải quyết hoặc bất kỳ lời hứa nào trong số chúng bị từ chối.
Ví dụ: giả sử rằng bạn có mười lời hứa (Hoạt động không đồng bộ để thực hiện cuộc gọi mạng hoặc kết nối cơ sở dữ liệu). Bạn phải biết khi nào tất cả các lời hứa được giải quyết hoặc bạn phải đợi cho đến khi tất cả các lời hứa được giải quyết. Vì vậy, bạn đang chuyển tất cả mười lời hứa cho Promise.all. Sau đó, bản thân Promise.all là một lời hứa sẽ được giải quyết sau khi tất cả mười lời hứa được giải quyết hoặc bất kỳ lời hứa nào trong số mười lời hứa bị từ chối do lỗi.
Hãy xem nó trong mã:
Promise.all([Promise1, Promise2, Promise3])
.then(result) => {
console.log(result)
})
.catch(error => console.log(`Error in promises ${error}`))
Như bạn có thể thấy, chúng tôi đang chuyển một mảng tới Promise.all. Và khi cả ba lời hứa được giải quyết, Promise.all sẽ giải quyết và đầu ra được điều khiển.
Hãy xem một ví dụ:
// A simple promise that resolves after a given time
const timeOut =
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(`Completed in ${t}`)
}, t)
})
}
// Resolving a normal promise.
timeOut(1000)
.then(result => console.log(result)) // Completed in 1000
// Promise.all
Promise.all([timeOut(1000), timeOut(2000)])
.then(result => console.log(result)) // ["Completed in 1000", "Completed in 2000"]
Trong ví dụ trên, Promise.all giải quyết sau 2000 mili giây và đầu ra được điều khiển dưới dạng một mảng.
Một điều thú vị về Promise.all là thứ tự của các lời hứa được duy trì. Lời hứa đầu tiên trong mảng sẽ được giải quyết thành phần tử đầu tiên của mảng đầu ra, lời hứa thứ hai sẽ là phần tử thứ hai trong mảng đầu ra, v.v.
Hãy xem một ví dụ khác:
// A simple promise that resolves after a given time
const timeOut =
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(`Completed in ${t}`)
}, t)
})
}
const durations = [1000, 2000, 3000]
const promises = []
durations.map((duration) => {
// In the below line, two things happen.
// 1. We are calling the async function (timeout()). So at this point the async function has started and enters the 'pending' state.
// 2. We are pushing the pending promise to an array.
promises.push(timeOut(duration))
})
console.log(promises) // [ Promise { "pending" }, Promise { "pending" }, Promise { "pending" } ]
// We are passing an array of pending promises to Promise.all
// Promise.all will wait till all the promises get resolves and then the same gets resolved.
Promise.all(promises)
.then(response => console.log(response)) // ["Completed in 1000", "Completed in 2000", "Completed in 3000"]
Từ ví dụ trên, rõ ràng là Promise.all đợi cho đến khi tất cả các lời hứa được giải quyết.
Hãy xem điều gì sẽ xảy ra nếu bất kỳ lời hứa nào bị từ chối.
// A simple promise that resolves after a given time
const timeOut =
return new Promise((resolve, reject) => {
setTimeout(() => {
if (t === 2000) {
reject(`Rejected in ${t}`)
} else {
resolve(`Completed in ${t}`)
}
}, t)
})
}
const durations = [1000, 2000, 3000]
const promises = []
durations.map((duration) => {
promises.push(timeOut(duration))
})
// We are passing an array of pending promises to Promise.all
Promise.all(promises)
.then(response => console.log(response)) // Promise.all cannot be resolved, as one of the promises passed got rejected.
.catch(error => console.log(`Error in executing ${error}`)) // Promise.all throws an error.
Như bạn có thể thấy, nếu một trong những lời hứa thất bại, thì tất cả những lời hứa còn lại sẽ thất bại. Sau đó, Promise.all bị từ chối.
Đối với một số trường hợp sử dụng, bạn không cần điều đó. Bạn cần thực hiện tất cả các lời hứa ngay cả khi một số lời hứa không thành công hoặc có thể bạn có thể xử lý các lời hứa không thành công sau này.
Hãy xem làm thế nào để xử lý điều đó.
const durations = [1000, 2000, 3000]
promises = durations.map((duration) => {
return timeOut(duration).catch(e => e) // Handling the error for each promise.
})
Promise.all(promises)
.then(response => console.log(response)) // ["Completed in 1000", "Rejected in 2000", "Completed in 3000"]
.catch(error => console.log(`Error in executing ${error}`))
view raw
Các trường hợp sử dụng Promise.all
Giả sử rằng bạn phải thực hiện một số lượng lớn các thao tác Async như gửi email tiếp thị hàng loạt tới hàng nghìn người dùng.
Mã giả đơn giản sẽ là:
for (let i=0;i<50000; i += 1) {
sendMailForUser(user[i]) // Async operation to send a email
}
Ví dụ trên là đơn giản. Nhưng nó không hiệu quả lắm. Ngăn xếp sẽ trở nên quá nặng và tại một thời điểm, JavaScript sẽ có một số lượng lớn kết nối HTTP đang mở có thể giết chết máy chủ.
Một cách tiếp cận hiệu quả đơn giản sẽ là thực hiện theo đợt. Lấy 500 người dùng đầu tiên, kích hoạt thư và đợi cho đến khi tất cả các kết nối HTTP được đóng lại. Và sau đó lấy đợt tiếp theo để xử lý nó, v.v.
Hãy xem một ví dụ:
// Async function to send mail to a list of users.
const sendMailForUsers = async (users) => {
const usersLength = users.length
for (let i = 0; i < usersLength; i += 100) {
const requests = users.slice(i, i + 100).map((user) => { // The batch size is 100. We are processing in a set of 100 users.
return triggerMailForUser(user) // Async function to send the mail.
.catch(e => console.log(`Error in sending email for ${user} - ${e}`)) // Catch the error if something goes wrong. So that it won't block the loop.
})
// requests will have 100 or less pending promises.
// Promise.all will wait till all the promises got resolves and then take the next 100.
await Promise.all(requests)
.catch(e => console.log(`Error in sending email for the batch ${i} - ${e}`)) // Catch the error.
}
}
sendMailForUsers(userLists)
Hãy xem xét một kịch bản khác: Bạn phải xây dựng một API nhận thông tin từ nhiều API của bên thứ ba và tổng hợp tất cả phản hồi từ các API đó.
Promise.all là cách hoàn hảo để làm điều đó. Hãy xem làm thế nào.
// Function to fetch Github info of a user.
const fetchGithubInfo = async (url) => {
console.log(`Fetching ${url}`)
const githubInfo = await axios(url) // API call to get user info from Github.
return {
name: githubInfo.data.name,
bio: githubInfo.data.bio,
repos: githubInfo.data.public_repos
}
}
// Iterates all users and returns their Github info.
const fetchUserInfo = async (names) => {
const requests = names.map((name) => {
const url = `https://api.github.com/users/${name}`
return fetchGithubInfo(url) // Async function that fetches the user info.
.then((a) => {
return a // Returns the user info.
})
})
return Promise.all(requests) // Waiting for all the requests to get resolved.
}
fetchUserInfo(['sindresorhus', 'yyx990803', 'gaearon'])
.then(a => console.log(JSON.stringify(a)))
/*
Output:
[{
"name": "Sindre Sorhus",
"bio": "Full-Time Open-Sourcerer ·· Maker ·· Into Swift and Node.js ",
"repos": 996
}, {
"name": "Evan You",
"bio": "Creator of @vuejs, previously @meteor & @google",
"repos": 151
}, {
"name": "Dan Abramov",
"bio": "Working on @reactjs. Co-author of Redux and Create React App. Building tools for humans.",
"repos": 232
}]
*/
Tóm lại, Promise.all là cách tốt nhất để tổng hợp một nhóm các lời hứa thành một lời hứa duy nhất. Đây là một trong những cách đạt được đồng thời trong JavaScript.
Hy vọng bạn thích bài viết này. Nếu bạn đã làm, xin vui lòng vỗ tay và chia sẻ nó.
Ngay cả khi bạn không làm, điều đó vẫn ổn, bạn vẫn có thể làm điều đó 😛