سلام دوستان خوبم وقتتون بخیر 🤗 . شاید بعضی وقتا براتون این اتفاق افتاده باشه که Dataیی رو دارید از یه Api میگیرید و برای اینکه روی اون Data تغییراتی انجام بدید باید روی اون map بزنید و توی این mapی که دارید میزنید هر loop که بخواد اجرا بشه باید loop قبلیش تکمیل شده باشه . در حالت معمول که بخواین از map استفاده کنین به یکسری مشکل میخورید 😕 ولی اگه به حرف من گوش کنین به مشکل نمیخورین 🥳 پس با من همراه باشین.
در مرحله اول من اینجا یه تابع دارم که در واقع اومده همون Api مورد نظرمون رو که میخوایم روش map بزنیم رو شبیه سازی کرده که همانطور که در پایین می بینید ما یک آرایه از نام های کاربری و یک تابع داریم که یک پارامتر را می گیره و یک رشته را برمی گردونه.
const usernames: string[] = ["jordanrjdev", "anonymous123", "channelyy"]; const simulateFetchData = (username: string): Promise<string> => { return new Promise((resolve) => { setTimeout(() => { resolve(`${username} is a valid username`); }, 1000); }); }
حالا میخوایم آرایه نام کاربری را تکرار کنیم تا داده های شبیه سازی شده هر کاربر رو با map بدست بیاریم.
const dataUsers = usernames.map(async (username) => { return await simulateFetchData(username); }); console.log(dataUsers);
حالا بریم با هم log مون رو ببینیم تا ببینیم خروجی چیه !
[ Promise { <pending> }, Promise { <pending> }, Promise { <pending> } ]
بله همونطور که مشاهده میکنید خروجی چیزی جز Promise های pending نیست که داره بهمون نشون میده
این هم بخاطر ماهیت async/await هست که از Promise استفاده میکنه و اینطور هست که توی هر گام از حلقه map مون صبر نمیکنه تا نتیجه Promise برگرده به همین خاطر همه گام های map مون در حالت معلق ( pending ) باقی می مونن.حالا چاره چیه؟?
راه حل اول استفاده از حلقه for..of هست بریم با هم ببینیم چجوری پیاده سازی میشه
const getWithForOf = async() => { console.time("for of"); const data = []; for (const username of usernames) { let dataUser = await simulateFetchData(username); data.push(dataUser); } console.timeEnd("for of"); } getWithForOf();
این راه حل خوبیه و رایج هست ولی مشکلی که داره زمان زیادی رو صرف میکنه تا اون چیزی رو که میخوایم رو بهمون بده ? توی کد دقت کنین از console.time استفاده کردم تا ببینید زمانی که صرف میشه چقد هست که خروجی که بهمون نشون میده به نظرم فاجعه س ? چون حالا این یه تابع ساده بیشتر نیست که انقد زمان صرفش کرده، واسه Data ی واقعی میخواد چیکار کنه ! ?
for of: 3.012s
راه حل دیگه ای که خودمم اون رو پیشنهاد میدم (حالا بازم بستگی به شرایط داره) استفاده از Promise.all هست، دوستان.
const getWithPromiseAll = async() => { console.time("promise all"); let data = await Promise.all(usernames.map(async (username) => { return await simulateFetchData(username); })) console.timeEnd("promise all"); } getWithPromiseAll();
خاطرتون هست که بهتون گفتم async/await از Promise استفاده میکنه ! یعنی این کدی که داشتیم
usernames.map(async (username) => { return await simulateFetchData(username); })
هر گام map مون، بهمون یه Promise برمیگردوند، درسته! حالا ما توی روش دوم اومدیم از Promise.all استفاده کردیم که بیاد تمامی Promise هامون رو نتیجه ش رو بهمون برگردونه ( فقط این نکته حواستون باشه Promise.all ببینه یکی از Promise ها reject شده باشه کل عملیاتش رو reject میکنه ، از بس که بی اعصابه این متد 😁) کلا این متد فوق العادس برای زمانی که کدهای Asynchronous دارید و میخواید بصورت موازی همه چی پیش بره.
حالا بریم ببینیم خروجی console.time مون چیه
promise all: 980.3000000119209ms
یعنی این دوستمون کار رو نذاشت به یک ثانیه هم برسه 🤩
همونطور که من همیشه میگم و الانم میگم بهتون، با قطعیت نمیگم که کدوم روش بهتره ، اگه شرایط Performance رو در نظر بگیریم خب طبیعتاً Promise.all عالیه ولی یه مواقعی ممکنه بخواید از حلقه for استفاده کنید پس اون موقع چاره ای جز این نداریم که از for..of استفاده کنیم . خب دیگه امیدوارم دوستان این مقاله براتون مفید بوده باشه و جا داره اینجا از دوست و همکار خوبم محمدرضا تشکر کنم که باعث شد این مقاله رو بنویسم و همینطور دوستمون Jordan Jaramillo . شب و روز تون خوش مراقب خودتون باشید 💐.