Все, що вам потрібно знати про Promise.all

Обіцянки в JavaScript - це один із потужних API, який допомагає нам виконувати операції Async.

Promise.all виводить операції Async на новий рівень, оскільки це допомагає вам об’єднати групу обіцянок.

Іншими словами, я можу сказати, що це допомагає вам робити паралельні операції (іноді безкоштовно).

Передумови:

Ви повинні знати, що таке обіцянка в JavaScript.

Що таке Promise.all?

Promise.all насправді є обіцянкою, яка приймає масив обіцянок як вхід (ітерабельний). Потім це вирішується, коли всі обіцянки вирішуються або будь-яка з них отримує відмову.

Наприклад, припустимо, що у вас є десять обіцянок (операція Async для здійснення мережевого дзвінка або підключення до бази даних). Ви повинні знати, коли всі обіцянки будуть вирішені, або вам доведеться чекати, поки всі обіцянки вирішаться. Отже, ви передаєте всі десять обіцянок Promise.all. Потім Promise.all сам як обіцянку буде вирішено, як тільки всі десять обіцянок будуть вирішені або будь-яка з десяти обіцянок буде відхилена з помилкою.

Давайте подивимося в коді:

Promise.all([Promise1, Promise2, Promise3]) .then(result) => { console.log(result) }) .catch(error => console.log(`Error in promises ${error}`))

Як бачите, ми передаємо масив Promise.all. І коли всі три обіцянки вирішуються, Promise.all вирішує і виведення втішається.

Подивимось приклад:

// A simple promise that resolves after a given time const timeOut = (t) => { 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"]

У наведеному вище прикладі Promise.all вирішує проблему після 2000 мс, а висновок консолюється як масив.

Одна цікава річ про Promise.all полягає в тому, що порядок обіцянок підтримується. Перша обіцянка в масиві буде розв’язана з першим елементом вихідного масиву, друга обіцянка буде другим елементом у вихідному масиві тощо.

Побачимо ще один приклад:

// A simple promise that resolves after a given time const timeOut = (t) => { 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"] 

З наведеного вище прикладу ясно, що Promise.all чекає, поки всі обіцянки не вирішаться.

Давайте подивимося, що станеться, якщо будь-яку з обіцянок буде відхилено.

// A simple promise that resolves after a given time const timeOut = (t) => { 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. 

Як бачите, якщо одна з обіцянок не виконується, то всі інші обіцянки провалюються. Тоді Promise.all отримує відмову.

Для деяких випадків використання це вам не потрібно. Вам потрібно виконати всі обіцянки, навіть якщо деякі з них зазнали невдачі, або, можливо, ви зможете впоратися з невдалими обіцянками пізніше.

Давайте подивимося, як з цим впоратися.

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

Варіанти використання Promise.all

Припустимо, що вам доведеться виконати величезну кількість операцій Async, таких як надсилання масових маркетингових електронних листів тисячам користувачів.

Простий псевдокод буде:

for (let i=0;i<50000; i += 1) { sendMailForUser(user[i]) // Async operation to send a email }

Наведений приклад є простим. Але це не дуже ефективно. Стек стане занадто важким, і в один момент часу JavaScript матиме величезну кількість відкритих HTTP-з'єднань, що може вбити сервер.

Простим ефективним підходом було б робити це партіями. Візьміть перших 500 користувачів, запустіть пошту та зачекайте, поки не закриються всі з'єднання HTTP. А потім візьміть наступну партію для її обробки тощо.

Подивимось приклад:

// Async function to send mail to a list of users. const sendMailForUsers = async (users) => { const usersLength = users.length for (let i = 0; i  { // 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)

Давайте розглянемо інший сценарій: вам потрібно побудувати API, який отримує інформацію від декількох сторонніх API та агрегує всі відповіді від API.

Promise.all - ідеальний спосіб зробити це. Подивимось як.

// 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 = `//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 }] */ 

На закінчення Promise.all - найкращий спосіб об’єднати групу обіцянок в одну обіцянку. Це один із способів досягнення паралельності в JavaScript.

Сподіваюся, вам сподобалась ця стаття. Якщо ви це зробили, будь ласка, поплескайте і поділіться цим.

Навіть якщо ви цього не зробили, це нормально, ви все одно можете це зробити: P