Promise.allについて知っておくべきことすべて

JavaScriptのPromiseは、非同期操作を実行するのに役立つ強力なAPIの1つです。

Promise.allは、Promiseのグループを集約するのに役立つため、非同期操作を次の新しいレベルに引き上げます。

つまり、並行操作(場合によっては無料)を行うのに役立つと言えます。

前提条件:

JavaScriptのPromiseとは何かを知っている必要があります。

Promise.allとは何ですか?

Promise.allは、実際には、promiseの配列を入力(反復可能)として受け取るpromiseです。次に、すべての約束が解決されるか、いずれかが拒否されると、解決されます。

たとえば、10個のpromise(ネットワーク呼び出しまたはデータベース接続を実行するための非同期操作)があるとします。すべての約束がいつ解決されるかを知る必要があります。または、すべての約束が解決されるまで待つ必要があります。つまり、10個の約束すべてをPromise.allに渡すことになります。次に、10個のpromiseがすべて解決されるか、10個のpromiseのいずれかがエラーで拒否されると、Promise.all自体がpromiseとして解決されます。

コードで見てみましょう:

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

ご覧のとおり、Promise.allに配列を渡しています。そして、3つのpromiseがすべて解決されると、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の興味深い点の1つは、約束の順序が維持されていることです。配列の最初のpromiseは出力配列の最初の要素に解決され、2番目のpromiseは出力配列の2番目の要素になります。

別の例を見てみましょう:

// 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がすべてのpromiseが解決するまで待機することは明らかです。

いずれかの約束が拒否された場合にどうなるか見てみましょう。

// 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. 

ご覧のとおり、プロミスの1つが失敗すると、残りのすべてのプロミスが失敗します。その後、Promise.allは拒否されます。

一部のユースケースでは、それは必要ありません。一部が失敗した場合でも、すべてのPromiseを実行する必要があります。そうでない場合は、失敗したPromiseを後で処理できます。

それを処理する方法を見てみましょう。

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のユースケース

何千人ものユーザーに大量のマーケティングメールを送信するなど、膨大な数の非同期操作を実行する必要があると想定します。

単純な擬似コードは次のようになります。

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は、プロミスのグループを1つのプロミスに集約するための最良の方法です。これは、JavaScriptで並行性を実現する方法の1つです。

この記事が気に入っていただけたでしょうか。もしそうなら、拍手して共有してください。

あなたがしなかったとしても、それはあなたがとにかくそれをすることができます:P