Promise.all در جاوا اسکریپت — از صفر تا صد

۱۸۹ بازدید
آخرین به‌روزرسانی: ۰۸ شهریور ۱۴۰۲
زمان مطالعه: ۵ دقیقه
Promise.all در جاوا اسکریپت — از صفر تا صد

Promise-ها در جاوا اسکریپت یکی از API-های قدرتمند هستند که به اجرای عملیات ناهمگام کمک می‌کنند. Promise.all عملیات ناهمگام را به سطح بالاتری ارتقا داده است و به گروه‌بندی promise-ها کمک کرده است. به بیان دیگر با استفاده از Promise.all در جاوا اسکریپت می‌توان گروهی از عملیات «همزمان» (concurrent) را اجرا کرد.

پیش‌نیاز مطالعه این مطلب آن است که با مفهوم Promise در جاوا اسکریپت آشنا باشید. اگر چنین نیست پیشنهاد می‌کنیم، مقاله «Promise در جاوا اسکریپت و کاربردهای آن — به زبان ساده» را مطالعه کنید.

Promise.all چیست؟

Promise.all در واقع یک promise است که ارائه از Promise-ها را به عنوان ورودی (عنصر تکرارپذیر = iterable) می‌گیرد. سپس وقتی که همه promise-ها پاسخ داده شوند یا هر یک از آن‌ها رد شوند، Promise.all نیز به پایان می‌رسد.

برای نمونه فرض کنید 10 Promise دارید که عملیات ناهمگامی برای یک فراخوانی شبکه یا اتصال پایگاه داده اجرا می‌کنند. لازم است که بدانید همه Promise-ها چه زمانی پاسخ داده می‌شوند و یا لازم است صبر کنید تا همه آن‌ها به پایان برسند. بدین ترتیب همه آن‌ها را به Promise.all می‌فرستید و سپس Promise.all خودش به عنوان یک Promise زمانی که هر 10 Promise پاسخ داده شوند و یا هر یک از آن‌ها رد شوند، به پایان می‌رسد.

به کد زیر توجه کنید:

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

همان طور که می‌بینید، ما یک ارائه را به Promise.all ارسال کرده‌ایم و زمانی که هر سه Promise پاسخ داده شوند، Promise.all نیز پایان می‌یابد و خروجی در کنسول ارائه می‌شود.

به مثال زیر نیز توجه کنید:

1// A simple promise that resolves after a given time
2const timeOut = (t) => {
3  return new Promise((resolve, reject) => {
4    setTimeout(() => {
5      resolve(`Completed in ${t}`)
6    }, t)
7  })
8}
9
10// Resolving a normal promise.
11timeOut(1000)
12 .then(result => console.log(result)) // Completed in 1000
13
14// Promise.all
15Promise.all([timeOut(1000), timeOut(2000)])
16 .then(result => console.log(result)) // ["Completed in 1000", "Completed in 2000"]

در مثال فوق، Promise.all در طی 2000 میلی‌ثانیه پس از این که خروجی به صورت یک ارائه در کنسول ارائه شود پایان می‌یابد.

ترتیب Promise-ها

یک نکته جالب در مورد Promise.all این است که ترتیب Promise-ها در آن حفظ می‌شود. نخستین Promise در ارائه به عنوان نخستین عنصر ارائه خروجی ارائه می‌شود، Promise دوم عنصر دوم ارائه است و همین طور تا آخر.

به مثال زیر نیز توجه کنید:

1// A simple promise that resolves after a given time
2const timeOut = (t) => {
3  return new Promise((resolve, reject) => {
4    setTimeout(() => {
5      resolve(`Completed in ${t}`)
6    }, t)
7  })
8}
9
10const durations = [1000, 2000, 3000]
11
12const promises = []
13
14durations.map((duration) => {
15  // In the below line, two things happen.
16  // 1. We are calling the async function (timeout()). So at this point the async function has started and enters the 'pending' state.
17  // 2. We are pushing the pending promise to an array.
18  promises.push(timeOut(duration)) 
19})
20
21console.log(promises) // [ Promise { "pending" }, Promise { "pending" }, Promise { "pending" } ]
22
23// We are passing an array of pending promises to Promise.all
24// Promise.all will wait till all the promises get resolves and then the same gets resolved.
25Promise.all(promises)
26.then(response => console.log(response)) // ["Completed in 1000", "Completed in 2000", "Completed in 3000"]

همان طور که می‌بینید اگر یکی از Promise-ها رد شود، همه موارد باقیمانده Promise-ها نیز شکست می‌خورند. در این صورت Promise.all نیز رد خواهد شد.

در برخی موارد ما به چنین موقعیتی نیاز نداریم. در واقع در این موارد لازم است که همه Promise-ها صرف نظر از این که یکی از آن‌ها رد شود یا نه اجرا شوند و احتمالاً مورد رد شده را نیز در ادامه می‌توان مدیریت کرد. مدیریت این وضعیت به صورت زیر ممکن است:

1const durations = [1000, 2000, 3000]
2
3promises = durations.map((duration) => {
4  return timeOut(duration).catch(e => e) // Handling the error for each promise.
5})
6
7Promise.all(promises)
8  .then(response => console.log(response)) // ["Completed in 1000", "Rejected in 2000", "Completed in 3000"]
9  .catch(error => console.log(`Error in executing ${error}`))

کاربردهای Promise.all

فرض کنید لازم است تعداد زیادی عملیات ناهمگام مانند ارسال انبوه ایمیل‌های بازاریابی به هزاران کاربر را انجام دهید.

شبه کد ساده آن چنین می‌تواند باشد:

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

مثال فوق سرراست است. اما چندان کارآمد نیست. در نقطه‌ای از زمان، پشته بسیار سنگین می‌شود و جاوا اسکریپت با انبوهی از درخواست‌های باز اتصال HTTP مواجه خواهد بود که می‌توانند سرور را کار بیندازند.

یک رویکرد ساده و کارآمد می‌تواند این باشد که این کار در دسته‌بندی‌های مختلف انجام یابد. 500 کاربرِ نخست انتخاب و ایمیل‌ها ارسال می‌شوند و صبر می‌کنیم تا همه اتصال‌های HTTP بسته شوند. سپس دسته بعدی را پردازش می‌کنیم و همین طور تا آخر. مثال زیر را در نظر بگیرید:

1// Async function to send mail to a list of users.
2const sendMailForUsers = async (users) => {
3  const usersLength = users.length
4  
5  for (let i = 0; i < usersLength; i += 100) { 
6    const requests = users.slice(i, i + 100).map((user) => { // The batch size is 100. We are processing in a set of 100 users.
7      return triggerMailForUser(user) // Async function to send the mail.
8       .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.
9    })
10    
11    // requests will have 100 or less pending promises. 
12    // Promise.all will wait till all the promises got resolves and then take the next 100.
13    await Promise.all(requests)
14     .catch(e => console.log(`Error in sending email for the batch ${i} - ${e}`)) // Catch the error.
15  }
16}
17
18
19sendMailForUsers(userLists)

سناریوی دیگری را در نظر بگیرید. فرض کنید لازم است یک API بسازیم که اطلاعاتی را از API-های شخص ثالث می‌گیرد و همه پاسخ‌ها را از آن API-ها تجمیع می‌کند.

در این حالت Promise.all بهترین روش برای انجام این کار است. چگونگی آن را در کد زیر ملاحظه می‌کنید:

1// Function to fetch Github info of a user.
2const fetchGithubInfo = async (url) => {
3  console.log(`Fetching ${url}`)
4  const githubInfo = await axios(url) // API call to get user info from Github.
5  return {
6    name: githubInfo.data.name,
7    bio: githubInfo.data.bio,
8    repos: githubInfo.data.public_repos
9  }
10}
11
12// Iterates all users and returns their Github info.
13const fetchUserInfo = async (names) => {
14  const requests = names.map((name) => {
15    const url = `https://api.github.com/users/${name}`
16    return fetchGithubInfo(url) // Async function that fetches the user info.
17     .then((a) => {
18      return a // Returns the user info.
19      })
20  })
21  return Promise.all(requests) // Waiting for all the requests to get resolved.
22}
23
24
25fetchUserInfo(['sindresorhus', 'yyx990803', 'gaearon'])
26 .then(a => console.log(JSON.stringify(a)))
27
28/*
29Output:
30[{
31  "name": "Sindre Sorhus",
32  "bio": "Full-Time Open-Sourcerer ·· Maker ·· Into Swift and Node.js ",
33  "repos": 996
34}, {
35  "name": "Evan You",
36  "bio": "Creator of @vuejs, previously @meteor & @google",
37  "repos": 151
38}, {
39  "name": "Dan Abramov",
40  "bio": "Working on @reactjs. Co-author of Redux and Create React App. Building tools for humans.",
41  "repos": 232
42}]
43*/

سخن پایانی

در پایان باید اشاره کنیم که Promise.all بهترین روش برای تجمیع یک گروه از Promise-ها در یک Promise منفرد است. این یکی از بهترین روش‌ها برای رسیدن به «همزمانی» (Concurrency) در جاوا اسکریپت است. امیدواریم از مطالعه این نوشته بهره برده باشید.

اگر این مطلب برای شما مفید بوده است، آموزش‌های زیر نیز به شما پیشنهاد می‌شوند:

==

بر اساس رای ۱ نفر
آیا این مطلب برای شما مفید بود؟
اگر بازخوردی درباره این مطلب دارید یا پرسشی دارید که بدون پاسخ مانده است، آن را از طریق بخش نظرات مطرح کنید.
منابع:
medium.freecodecamp
نظر شما چیست؟

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *