سایت خبرکاو

جستجوگر هوشمند اخبار و مطالب فناوری

چگونه وعده های جاوا اسکریپت کار می کند – کتابچه راهنمای مبتدیان

بسیاری از عملیات، مانند درخواست های شبکه، ماهیت ناهمزمان دارند. یکی از مفیدترین و قدرتمندترین ابزارها برای کار با کدهای ناهمزمان Promise است. در این کتاب راهنما، همه چیز را در مورد وعده های جاوا اسکریپت و نحوه استفاده از آنها خواهید آموخت. فهرست مطالب وعده چیست؟ مقایسه Promises با سایر الگوهای Async چگونه یک وعده ایجاد کنیم چگونه نتیجه یک قول را بدست آوریم نحوه رسیدگی به خطاها با then وعده زنجیر چگونه می توان وعده های فوری انجام شده یا ...

بسیاری از عملیات، مانند درخواست های شبکه، ماهیت ناهمزمان دارند. یکی از مفیدترین و قدرتمندترین ابزارها برای کار با کدهای ناهمزمان Promise است. در این کتاب راهنما، همه چیز را در مورد وعده های جاوا اسکریپت و نحوه استفاده از آنها خواهید آموخت.

فهرست مطالب

    وعده چیست؟

    مقایسه Promises با سایر الگوهای Async

    چگونه یک وعده ایجاد کنیم

    چگونه نتیجه یک قول را بدست آوریم

    نحوه رسیدگی به خطاها با then

    وعده زنجیر

    چگونه می توان وعده های فوری انجام شده یا رد شده را ایجاد کرد

    نحوه استفاده از async و await

    وعده ضد الگوها

    خلاصه

وعده چیست؟

بیایید با نگاهی به اینکه وعده چیست شروع کنیم.

به زبان ساده، Promise یک شی است که یک عملیات ناهمزمان را نشان می دهد. این شی می تواند به شما بگوید چه زمانی عملیات موفقیت آمیز است یا چه زمانی شکست می خورد.

وقتی یک API مبتنی بر Promise را فراخوانی می کنید، تابع یک شی Promise را برمی گرداند که در نهایت نتیجه عملیات را ارائه می دهد.

کشورهای قول

در طول عمر خود، یک Promise می تواند در یکی از سه حالت باشد:

در حال تعلیق : در حالی که عملیات هنوز در حال انجام است، یک وعده در انتظار است. در حالت بیکار است و منتظر نتیجه نهایی (یا خطا) است.

انجام شد : وظیفه ناهمزمانی که Promise را برگرداند با موفقیت انجام شد. یک وعده با مقداری که نتیجه عملیات است محقق می شود.

Rejected : اگر عملیات ناهمزمان با شکست مواجه شد، گفته می شود که Promise رد می شود. یک قول با دلیل رد می شود. این معمولاً یک شی Error است، اما یک Promise را می توان با هر مقداری رد کرد - حتی یک عدد یا رشته ساده!

یک Promise در حالت معلق شروع می شود، سپس بسته به نتیجه، به حالت انجام شده یا رد شده منتقل می شود. گفته می شود که یک وعده پس از رسیدن به حالت تحقق یافته یا رد شده، حل و فصل می شود.

البته، هیچ تضمینی وجود ندارد که کار ناهمزمان هرگز کامل شود. کاملاً ممکن است که یک Promise برای همیشه در حالت معلق باقی بماند، اگرچه این به دلیل وجود اشکال در کد کار ناهمزمان است.

مقایسه Promises با سایر الگوهای Async

Promises کمی متفاوت از سایر الگوهای ناهمزمان در جاوا اسکریپت عمل می کند. قبل از غواصی عمیق تر در Promises، اجازه دهید به طور خلاصه Promises را با این تکنیک های دیگر مقایسه کنیم.

توابع پاسخ به تماس

تابع فراخوانی تابعی است که به تابع دیگری منتقل می کنید. هنگامی که تابعی که فراخوانی می کنید کار خود را به پایان رساند، تابع پاسخ به تماس شما را با نتیجه اجرا می کند.

تابعی به نام getUsers را تصور کنید که یک درخواست شبکه برای دریافت آرایه ای از کاربران می دهد. می‌توانید یک تابع callback به getUsers ارسال کنید، که پس از تکمیل درخواست شبکه، با آرایه‌ای از کاربران فراخوانی می‌شود:

 console.log('Preparing to get users'); getUsers(users => { console.log('Got users:', users); }); console.log('Users request sent');
نمونه ای از یک تابع فراخوانی

ابتدا کد بالا "آماده سازی برای دریافت کاربران" را چاپ می کند. سپس getUsers را فراخوانی می کند که درخواست شبکه را آغاز می کند. اما جاوا اسکریپت منتظر تکمیل درخواست نمی ماند. در عوض، بلافاصله دستور بعدی console.log را اجرا می کند.

بعداً، پس از بارگیری کاربران، پاسخ تماس شما اجرا می‌شود و «کاربران دریافت شده» چاپ می‌شود.

برخی از APIهای مبتنی بر تماس، مانند بسیاری از APIهای Node.js، از تماس‌های اول خطا استفاده می‌کنند. این توابع فراخوانی دو آرگومان می گیرند. آرگومان اول یک خطا است و دومی نتیجه.

به طور معمول، بسته به نتیجه عملیات، تنها یکی از اینها دارای ارزش است. این شبیه به حالات وعده محقق شده و رد شده است.

مشکل با callback API ها در تودرتو است. اگر نیاز به برقراری چند تماس ناهمزمان به صورت متوالی داشته باشید، در نهایت با فراخوانی‌ها و تماس‌های تودرتو با عملکرد مواجه خواهید شد.

تصور کنید می خواهید یک فایل را بخوانید، برخی از داده های آن فایل را پردازش کنید، سپس یک فایل جدید بنویسید. هر سه این وظایف ناهمزمان هستند و از یک API مبتنی بر پاسخ به تماس خیالی استفاده می کنند.

 readFile('sourceData.json', data => { processData(data, result => { writeFile(result, 'processedData.json', () => { console.log('Done processing'); }); }); });
دنباله ای از تماس های تو در تو

با رسیدگی به خطاها سخت تر می شود. تصور کنید که این توابع از تماس‌های اول خطا استفاده می‌کنند:

 readFile('sourceData.json', (error, data) => { if (error) { console.error('Error reading file:', error); return; } processData(data, (error, result) => { if (error) { console.error('Error processing data:', error); return; } writeFile(result, 'processedData.json', error => { if (error) { console.error('Error writing file:', error); return; } console.log('Done processing'); }); }); });
دنباله ای از تماس های اول خطای تودرتو

توابع Callback معمولاً مستقیماً به عنوان یک مکانیسم ناهمزمان در APIهای مدرن استفاده نمی شوند، اما همانطور که به زودی خواهید دید، آنها پایه و اساس انواع دیگر ابزارهای ناهمزمان مانند Promises هستند.

مناسبت ها

رویداد چیزی است که می توانید به آن گوش دهید و به آن پاسخ دهید. برخی از اشیاء در جاوا اسکریپت ساطع کننده رویداد هستند، به این معنی که می توانید شنوندگان رویداد را روی آنها ثبت کنید.

در DOM، بسیاری از عناصر رابط EventTarget را پیاده سازی می کنند که متدهای addEventListener و removeEventListener را ارائه می دهد.

یک نوع معین از رویداد می تواند بیش از یک بار رخ دهد. برای مثال، می‌توانید رویداد کلیک را روی یک دکمه گوش دهید:

 myButton.addEventListener('click', () => { console.log('button was clicked!'); });
گوش دادن برای یک کلیک روی یک دکمه

هر بار که دکمه کلیک می شود، متن "دکمه کلیک شد!" روی کنسول چاپ خواهد شد.

addEventListener خود تابع callback را می پذیرد. هر زمان که رویداد رخ می دهد، پاسخ تماس اجرا می شود.

یک شی می تواند چندین نوع رویداد را ساطع کند. یک شیء تصویری را در نظر بگیرید. اگر تصویر در URL مشخص شده با موفقیت بارگیری شود، رویداد load فعال می شود. اگر خطایی وجود داشته باشد، این رویداد راه اندازی نمی شود و در عوض رویداد error راه اندازی می شود.

 myImage.addEventListener('load', () => { console.log('Image was loaded'); }); myImage.addEventListener('error', error => { console.error('Image failed to load:', error); });
گوش دادن به بارگذاری تصویر و رویدادهای خطا

فرض کنید قبل از اینکه شنونده رویداد را اضافه کنید، تصویر بارگیری شده است. شما فکر میکنید چه اتفاقی خواهد افتاد؟ هیچ چی! یکی از اشکالات APIهای مبتنی بر رویداد این است که اگر بعد از یک رویداد شنونده رویداد اضافه کنید، پاسخ تماس شما اجرا نخواهد شد. پس از همه، این منطقی است - وقتی یک شنونده کلیک را به یک دکمه اضافه می کنید، نمی خواهید همه رویدادهای کلیک گذشته را دریافت کنید.

اکنون که تماس‌ها و رویدادها را تحلیل کردیم، بیایید نگاهی دقیق‌تر به Promises بیندازیم.

چگونه یک وعده ایجاد کنیم

می توانید با استفاده از کلمه کلیدی new با سازنده Promise یک Promise ایجاد کنید. سازنده Promise یک تابع callback می گیرد که دو آرگومان به نام های resolve و reject می گیرد. هر یک از این آرگومان‌ها تابعی هستند که توسط Promise ارائه می‌شوند، که برای انتقال Promise به حالت تحقق یافته یا رد شده استفاده می‌شوند.

در داخل تماس خود، کار ناهمزمان خود را انجام می دهید. در صورت موفقیت آمیز بودن کار، تابع resolve را با نتیجه نهایی فراخوانی می کنید. اگر خطایی وجود داشت، تابع reject را با خطا فراخوانی می کنید.

در اینجا یک مثال از ایجاد یک Promise است که تابع setTimeout مرورگر را می‌پیچد:

 function wait(duration) { return new Promise(resolve => { setTimeout(resolve, duration); }); }
بسته بندی setTimeout در یک وعده

تابع resolve به عنوان اولین آرگومان به setTimeout ارسال می شود. پس از سپری شدن زمان مشخص شده توسط duration ، مرورگر تابع resolve را فراخوانی می کند که Promise را برآورده می کند.

توجه: در این مثال، تاخیر قبل از فراخوانی تابع resolve ممکن است بیشتر از مدت زمان ارسال شده به تابع باشد. این به این دلیل است که setTimeout اجرای آن را در زمان مشخص شده تضمین نمی کند.

مهم است که توجه داشته باشید که اغلب اوقات، در واقع نیازی به ساختن Promise خود با دست ندارید. شما معمولاً با Promises بازگردانده شده توسط سایر APIها کار خواهید کرد.

چگونه نتیجه یک قول را بدست آوریم

ما نحوه ایجاد یک Promise را دیده‌ایم، اما چگونه می‌توان نتیجه عملیات ناهمزمان را به دست آورد؟ برای انجام این کار، then روی خود شی Promise فراخوانی می کنید. then یک تابع callback را به عنوان آرگومان خود می گیرد. هنگامی که Promise محقق شد، callback با نتیجه اجرا می شود.

بیایید نمونه ای از این را در عمل ببینیم. تابعی به نام getUsers را تصور کنید که به طور ناهمزمان فهرست ی از اشیاء کاربر را بارگذاری می کند و یک Promise برمی گرداند. می‌توانید با تماس با Promise then توسط getUsers بازگردانده شده است، فهرست کاربران را دریافت کنید.

 getUsers() .then(users => { console.log('Got users:', users); });
then با یک وعده تماس گرفت

درست مانند رویدادها یا API های مبتنی بر تماس، کد شما بدون انتظار برای نتیجه به اجرای خود ادامه می دهد. مدتی بعد، زمانی که کاربران بارگذاری شدند، پاسخ تماس شما برای اجرا برنامه ریزی می شود.

 console.log('Loading users'); getUsers() .then(users => { console.log('Got users:', users); }); console.log('Continuing on');

در مثال بالا ابتدا "Loading users" چاپ می شود. مورد بعدی که چاپ می شود "ادامه در" خواهد بود، زیرا تماس getUsers همچنان در حال بارگیری کاربران است. بعداً چاپ «کاربران دارم» را می‌بینید.

نحوه رسیدگی به خطاها با then

ما نحوه استفاده از then را برای دریافت نتیجه ارائه شده به Promise دیدیم، اما در مورد خطاها چطور؟ اگر نتوانیم فهرست کاربران را بارگیری کنیم چه اتفاقی می افتد؟

تابع then در واقع یک آرگومان دوم، یک callback دیگر را می گیرد. این کنترل کننده خطا است. اگر Promise رد شود، این callback با مقدار rejection اجرا می شود.

 getUsers() .then(users => { console.log('Got users:', users); }, error => { console.error('Failed to load users:', error); });

از آنجایی که یک Promise فقط می تواند انجام شود یا رد شود، اما نه هر دو، تنها یکی از این توابع پاسخ به تماس اجرا می شود.

این مهم است که هنگام کار با Promises همیشه خطاها را کنترل کنید. اگر یک Promise rejection دارید که با خطای فراخوانی کنترل نمی‌شود، در کنسول خود یک استثنا در مورد رد کردن کنترل نشده دریافت خواهید کرد که می‌تواند باعث ایجاد مشکلاتی برای کاربران شما در زمان اجرا شود.

وعده زنجیر

اگر بخواهید با چندین Promises به صورت سری کار کنید چه؟ مثال قبلی را در نظر بگیرید که در آن ما برخی از داده ها را از یک فایل بارگذاری کردیم، مقداری پردازش انجام دادیم و نتیجه را در یک فایل جدید نوشتیم. فرض کنید توابع readFile ، processData و writeFile از Promises به جای callback استفاده می کنند.

ممکن است چیزی شبیه به این را امتحان کنید:

 readFile('sourceData.json') .then(data => { processData(data) .then(result => { writeFile(result, 'processedData.json') .then(() => { console.log('Done processing'); }); }); });
وعده های تو در تو

این عالی به نظر نمی رسد، و ما هنوز مشکل تودرتویی را داریم که با رویکرد callback داشتیم. خوشبختانه راه بهتری وجود دارد. می توانید Promises را در یک توالی صاف به هم زنجیره بزنید.

برای اینکه ببینیم این چگونه کار می کند، بیایید عمیق تر به نحوه کارکرد then نگاه کنیم. ایده کلیدی این است: متد then Promise دیگری را برمی گرداند. هر مقداری که از then تماس خود برگردانید، به ارزش تحقق یافته این وعده جدید تبدیل می شود.

تابع getUsers را در نظر بگیرید که یک Promise را برمی‌گرداند که با آرایه‌ای از اشیاء کاربر محقق می‌شود. فرض کنید then روی این Promise فراخوانی می‌کنیم، و در بازگشت، اولین کاربر آرایه ( users[0] ) را برمی‌گردانیم:

 getUsers().then(users => users[0]);

پس ، کل این عبارت منجر به یک Promise جدید می شود که با اولین شی کاربر محقق می شود!

 getUsers() .then(users => users[0]) .then(firstUser => { console.log('First user:', firstUser.username); });
برگرداندن یک مقدار از then handler

این فرآیند بازگرداندن یک Promise، فراخوانی then و برگرداندن مقدار دیگری که منجر به Promise دیگری می شود، زنجیره ای نامیده می شود.

بیایید این ایده را گسترش دهیم. چه می‌شود اگر به جای برگرداندن مقداری از then ، Promise دیگری را برگردانیم؟ دوباره مثال پردازش فایل را در نظر بگیرید، جایی که readFile و processData هر دو توابع ناهمزمان هستند که Promises را برمی‌گردانند:

 readFile('sourceData.json') .then(data => processData(data));
از then وعده دیگری را برمی گرداند

کنترل کننده then processData فراخوانی می کند و Promise حاصل را برمی گرداند. مانند قبل، این یک وعده جدید را برمی گرداند. در این حالت، Promise جدید زمانی محقق می شود که Promise برگردانده شده توسط processData انجام شود و همان مقدار را به شما می دهد. پس کد در مثال بالا یک Promise را برمی‌گرداند که با داده‌های پردازش شده محقق می‌شود.

می توانید چندین Promises را یکی پس از دیگری زنجیره بزنید تا زمانی که به مقدار نهایی مورد نیاز خود برسید:

 readFile('sourceData.json') .then(data => processData(data)) .then(result => writeFile(result, 'processedData.json')) .then(() => console.log('Done processing'));
زنجیر زدن وعده های متعدد

در مثال بالا، کل عبارت منجر به یک Promise می شود که تا زمانی که داده های پردازش شده در یک فایل نوشته نشود، محقق نمی شود. "پردازش انجام شد!" روی کنسول چاپ می شود و سپس وعده نهایی محقق می شود.

رسیدگی به خطا در زنجیره‌های Promise

در مثال پردازش فایل ما، یک خطا در هر مرحله از فرآیند ممکن است رخ دهد. با استفاده از روش Promise's catch می توانید خطای هر مرحله از زنجیره Promise را مدیریت کنید.

 readFile('sourceData.json') .then(data => processData(data)) .then(result => writeFile(result, 'processedData.json')) .then(() => console.log('Done processing')) .catch(error => console.log('Error while processing:', error));
رسیدگی به خطاها با catch

اگر یکی از Promises در زنجیره رد شود، تابع callback ارسال شده برای catch اجرا می شود و بقیه زنجیره رد می شود.

نحوه استفاده finally

ممکن است کدی داشته باشید که بخواهید بدون توجه به نتیجه Promise آن را اجرا کنید. به عنوان مثال، شاید بخواهید یک پایگاه داده یا یک فایل را ببندید.

 openDatabase() .then(data => processData(data)) .catch(error => console.error('Error')) .finally(() => closeDatabase());

نحوه استفاده از Promise.all

زنجیره‌های Promise به شما امکان می‌دهند چندین کار را به ترتیب انجام دهید، اما اگر بخواهید چندین کار را همزمان اجرا کنید و منتظر بمانید تا همه آنها کامل شوند، چه؟ روش Promise.all به شما امکان می دهد این کار را انجام دهید.

Promise.all آرایه ای از Promises را می گیرد و یک Promise جدید برمی گرداند. این وعده زمانی محقق خواهد شد که تمام وعده های دیگر محقق شود. مقدار تحقق آرایه ای است که حاوی مقادیر تحقق هر Promise در آرایه ورودی است.

فرض کنید یک تابع loadUserProfile دارید که داده های نمایه کاربر را بارگیری می کند، و یک تابع دیگر loadUserPosts که پست های کاربر را بارگذاری می کند. هر دو یک شناسه کاربری را به عنوان آرگومان می گیرند. تابع سومی وجود دارد، renderUserPage ، که به نمایه و فهرست پست ها نیاز دارد.

 const userId = 100; const profilePromise = loadUserProfile(userId); const postsPromise = loadUserPosts(userId); Promise.all([profilePromise, postsPromise]) .then(results => { const [profile, posts] = results; renderUserPage(profile, posts); });
در انتظار چندین وعده با Promise.all

در مورد خطاها چطور؟ اگر هر یک از Promises ارسال شده به Promise.all با خطا رد شود، Promise حاصل نیز با آن خطا رد می شود. اگر هر یک از وعده های دیگر محقق شود، آن ارزش ها از بین می روند.

نحوه استفاده از Promise.allSettled

روش Promise.allSettled مشابه Promise.all کار می کند. تفاوت اصلی این است که Promise ارائه شده توسط Promise.allSettled هرگز رد نخواهد شد.

در عوض، با آرایه‌ای از اشیاء، که ترتیب آنها با ترتیب Promises در آرایه ورودی مطابقت دارد، تکمیل می‌شود. هر شی دارای یک ویژگی status است که بسته به نتیجه، یا "تکمیل شد" یا "رد شد".

اگر status "تحقق شده" باشد، شیء دارای یک value مقدار نیز خواهد بود که ارزش تحقق وعده را نشان می دهد. اگر status "رد شده" باشد، شی در عوض دارای یک ویژگی reason خواهد بود که همان خطا یا شی دیگری است که Promise با آن رد شده است.

دوباره تابع getUser را در نظر بگیرید که یک شناسه کاربری را می گیرد و یک Promise را برمی گرداند که با داشتن آن شناسه توسط کاربر محقق می شود. می‌توانید Promise.allSettled برای بارگیری موازی آنها استفاده کنید، و مطمئن شوید که همه کاربرانی که با موفقیت بارگیری شده‌اند را دریافت می‌کنید.

 Promise.allSettled([ getUser(1), getUser(2), getUser(3) ]).then(results => { const users = results .filter(result => result.status === 'fulfilled') .map(result => result.value); console.log('Got users:', users); });
تلاش برای بارگیری سه کاربر، و نشان دادن آنهایی که با موفقیت بارگیری شدند

می‌توانید یک تابع loadUsers با هدف کلی ایجاد کنید که با توجه به آرایه‌ای از شناسه‌های کاربر، کاربران را به‌طور موازی بارگیری می‌کند. این تابع یک Promise را برمی‌گرداند که با آرایه‌ای از همه کاربرانی که با موفقیت بارگذاری شده‌اند انجام می‌شود.

 function getUsers(userIds) { return Promise.allSettled(userIds.map(id => getUser(id))) .then(results => { return results .filter(result => result.status === 'fulfilled') .map(result => result.value); }); }
یک تابع کمکی برای بارگیری چندین کاربر به صورت موازی و فیلتر کردن هر درخواستی که شکست خورده است.

سپس، فقط می توانید getUsers با آرایه ای از شناسه های کاربری فراخوانی کنید:

 getUsers([1, 2, 3]) .then(users => console.log('Got users:', users));
با استفاده از تابع کمکی getUsers

چگونه می توان وعده های فوری انجام شده یا رد شده را ایجاد کرد

گاهی اوقات، ممکن است بخواهید ارزشی را در یک وعده محقق شده بپیچید. به عنوان مثال، ممکن است یک تابع ناهمزمان داشته باشید که یک Promise را برمی گرداند، اما یک مورد پایه وجود دارد که در آن مقدار را از قبل می دانید و نیازی به انجام هیچ کار ناهمزمانی ندارید.

برای این کار می توانید Promise.resolve با یک مقدار فراخوانی کنید. این یک Promise را برمی‌گرداند که بلافاصله با مقداری که شما مشخص کرده‌اید محقق می‌شود:

 Promise.resolve('hello') .then(result => { console.log(result); // prints "hello" });
با استفاده از Promise.resolve

این کم و بیش معادل موارد زیر است:

 new Promise(resolve => { resolve('hello'); }).then(result => { console.log(result); // also prints "hello" });

برای سازگارتر کردن API خود، می‌توانید یک Promise فوراً محقق شده ایجاد کنید و در چنین مواردی آن را برگردانید. به این ترتیب، کدی که تابع شما را فراخوانی می‌کند، می‌داند که همیشه انتظار یک Promise را دارد، مهم نیست که چه باشد.

به عنوان مثال، تابع getUsers را که قبلا تعریف شده بود در نظر بگیرید. اگر آرایه شناسه های کاربر خالی است، می توانید به سادگی یک آرایه خالی را برگردانید زیرا هیچ کاربری بارگیری نمی شود.

 function getUsers(userIds) { // immediately return the empty array if (userIds.length === 0) { return Promise.resolve([]); } return Promise.allSettled(userIds.map(id => getUser(id))) .then(results => { return results .filter(result => result.status === 'fulfilled') .map(result => result.value); }); }
گفت ن بازگشت اولیه به تابع کمکی getUsers

کاربرد دیگر Promise.resolve رسیدگی به مواردی است که در آن مقداری به شما داده می شود که ممکن است Promise باشد یا نباشد، اما می خواهید همیشه با آن به عنوان یک Promise رفتار کنید.

می‌توانید با خیال راحت Promise.resolve با هر مقداری تماس بگیرید. اگر قبلاً یک Promise بود، فقط Promise دیگری دریافت خواهید کرد که همان ارزش تحقق یا رد را دارد. اگر وعده ای نبود، در یک وعده وفای فوری پیچیده می شود.

مزیت این روش این است که نیازی به انجام چنین کاری ندارید:

 function getResult(result) { if (result.then) { result.then(value => { console.log('Result:', value); }); } else { console.log('Result:', result); } }
تماس مشروط then اساس اینکه آیا چیزی یک وعده است یا نه

به طور مشابه، می توانید یک Promise بلافاصله رد شده با Promise.reject ایجاد کنید. اگر یک بار دیگر به تابع getUsers برگردیم، شاید بخواهیم اگر آرایه شناسه کاربر null ، undefined یا آرایه نباشد، فوراً رد کنیم.

 function getUsers(userIds) { if (userIds == null || !Array.isArray(userIds)) { return Promise.reject(new Error('User IDs must be an array')); } // immediately return the empty array if (userIds.length === 0) { return Promise.resolve([]); } return Promise.allSettled(userIds.map(id => getUser(id))) .then(results => { return results .filter(result => result.status === 'fulfilled') .map(result => result.value); }); }
اگر آرگومان آرایه معتبری نباشد، خطا را برمی‌گرداند

نحوه استفاده از Promise.race

درست مانند Promise.all یا Promise.allSettled ، روش استاتیک Promise.race آرایه ای از Promises را می گیرد و یک Promise جدید برمی گرداند. همانطور که از نام آن پیداست، اگرچه تا حدودی متفاوت عمل می کند.

وعده ای که توسط Promise.race برگردانده می شود تا زمانی که اولین وعده از وعده های داده شده محقق شود یا رد شود، صبر می کند و سپس آن وعده نیز با همان مقدار محقق یا رد می شود. هنگامی که این اتفاق می افتد، ارزش های تحقق یافته یا رد شده سایر وعده ها از بین می روند.

نحوه استفاده از Promise.any

Promise.any مشابه Promise.race با یک تفاوت کلیدی کار می کند - جایی که Promise.race به محض تحقق یا رد شدن هر وعده انجام می شود، Promise.any منتظر اولین وعده انجام شده است.

نحوه استفاده از async و await

async و await کلمات کلیدی خاصی هستند که کار با Promises را ساده می کنند. آنها نیاز به توابع پاسخ به تماس و فراخوانی به then یا catch حذف می کنند. آنها با بلوک‌های try-catch نیز کار می‌کنند.

در اینجا نحوه کار آن آمده است. به جای then یک Promise، با قرار دادن کلمه کلیدی await قبل از آن، await آن هستید. این عملاً اجرای تابع را تا زمانی که Promise محقق شود، متوقف می کند.

در اینجا یک مثال با استفاده از Promises استاندارد آورده شده است:

 getUsers().then(users => { console.log('Got users:', users); });
در انتظار یک وعده با then

در اینجا کد معادل با استفاده از کلمه کلیدی await آمده است:

 const users = await getUsers(); console.log('Got users:', users);
در انتظار یک وعده با await

زنجیر وعده نیز کمی تمیزتر است:

 const data = await readFile('sourceData.json'); const result = await processData(data); await writeFile(result, 'processedData.json');
زنجیر زدن وعده ها با await

به یاد داشته باشید که هر استفاده از await اجرای بقیه عملکرد را متوقف می کند تا زمانی که وعده ای که در انتظار آن هستید محقق شود. اگر می خواهید منتظر چندین Promise باشید که به صورت موازی اجرا می شوند، می توانید از Promise.all استفاده کنید:

 const users = await Promise.all([getUser(1), getUser(2), getUser(3)]);
استفاده از Promise.all با await

برای استفاده از کلمه کلیدی await ، تابع شما باید به عنوان یک تابع ناهمگام علامت گذاری شود. می توانید این کار را با قرار دادن کلمه کلیدی async قبل از تابع خود انجام دهید:

 async function processData(sourceFile, outputFile) { const data = await readFile(sourceFile); const result = await processData(data); writeFile(result, outputFile); }
علامت گذاری یک تابع به عنوان async

گفت ن کلمه کلیدی async نیز تأثیر مهم دیگری بر عملکرد دارد. توابع Async همیشه به طور ضمنی یک Promise را برمی گرداند. اگر مقداری را از یک تابع async برگردانید، تابع در واقع یک Promise را برمی‌گرداند که با آن مقدار محقق شده است.

 async function add(a, b) { return a + b; } add(2, 3).then(sum => { console.log('Sum is:', sum); });
یک تابع async برای اضافه کردن دو عدد

در مثال بالا، تابع مجموع دو آرگومان a و b را برمی گرداند. اما از آنجایی که یک تابع async است، مجموع را برمی‌گرداند، بلکه یک Promise است که با مجموع انجام می‌شود.

رسیدگی به خطا با async و await

ما از await استفاده می کنیم تا منتظر باشیم تا Promise محقق شود، اما در مورد رسیدگی به خطاها چطور؟ اگر شما منتظر یک وعده هستید و آن را رد می کنید، یک خطا انجام می شود. این بدان معناست که برای رسیدگی به خطا، می توانید آن را در یک بلوک try-catch قرار دهید:

 try { const data = await readFile(sourceFile); const result = await processData(data); await writeFile(result, outputFile); } catch (error) { console.error('Error occurred while processing:', error); }
رسیدگی به خطا با بلوک try-catch

وعده ضد الگوها

ایجاد بیهوده یک وعده جدید

گاهی اوقات کاری برای ایجاد یک وعده جدید وجود ندارد. اما اگر قبلاً با Promises بازگردانده شده توسط API کار می کنید، معمولاً نیازی به ایجاد Promise خود ندارید:

 function getUsers() { return new Promise(resolve => { fetch('https://example.com/api/users') .then(result => result.json()) .then(data => resolve(data)) }); }
نمونه ای از خلق وعده غیر ضروری

در این مثال، ما یک Promise جدید برای بسته بندی Fetch API ایجاد می کنیم که قبلاً Promises را برمی گرداند. این غیر ضروری است. در عوض، فقط زنجیره Promise را مستقیماً از Fetch API برگردانید:

 function getUsers() { return fetch('https://example.com/api/users') .then(result => result.json()); }
با استفاده از وعده Fetch موجود

در هر دو مورد، کد فراخوانی getUsers یکسان به نظر می رسد:

 getUsers() .then(users => console.log('Got users:', users)) .catch(error => console.error('Error fetching users:', error));
کد مشتری برای هر یک از نسخه های تابع getUsers

خطاهای بلع

این نسخه از تابع getUsers را در نظر بگیرید:

 function getUsers() { return fetch('https://example.com/api/users') .then(result => result.json()) .catch(error => console.error('Error loading users:', error)); }
در حال بلعیدن خطای واکشی

رسیدگی به خطا خوب است، درست است؟ اگر این تابع getUsers را صدا کنیم، ممکن است از نتیجه شگفت زده شوید:

 getUsers() .then(users => console.log('Got users:', users)) .catch(error => console.error('error:', error);)
تماس گرفتن getUsers

ممکن است انتظار داشته باشید که "خطا" چاپ شود، اما در واقع "کاربران دریافت شده: تعریف نشده" را چاپ می کند. این به این دلیل است که catch call خطا را «بلع» می‌کند و یک Promise جدید را برمی‌گرداند که با مقدار بازگشتی catch back که undefined است ( console.error undefined را برمی‌گرداند) انجام می‌شود. شما همچنان پیام گزارش "خطا در بارگیری کاربران" را از getUsers خواهید دید، اما وعده بازگشتی محقق می شود، رد نمی شود.

اگر می‌خواهید خطای داخل تابع getUsers را دریافت کنید و همچنان Promise برگشتی را رد کنید، کنترل کننده catch باید یک Promise رد شده را برگرداند. می توانید این کار را با استفاده از Promise.reject انجام دهید.

 function getUsers() { return fetch('https://example.com/api/users') .then(result => result.json()) .catch(error => { console.error('Error loading users:', error); return Promise.reject(error); }); }
پس از رسیدگی به خطا، یک Promise رد شده را برگردانید

اکنون همچنان پیام «خطا در بارگیری کاربران» را دریافت خواهید کرد، اما Promise برگشتی نیز با این خطا رد خواهد شد.

وعده های تودرتو

از قرار دادن کد Promise خودداری کنید. در عوض، سعی کنید از زنجیرهای مسطح Promise استفاده کنید.

به جای این:

 readFile(sourceFile) .then(data => { processData(data) .then(result => { writeFile(result, outputFile) .then(() => console.log('done'); }); });

این کار را انجام دهید:

 readFile(sourceFile) .then(data => processData(data)) .then(result => writeFile(result, outputFile)) .then(() => console.log('done'));

خلاصه

در اینجا نکات کلیدی برای کار با Promises وجود دارد:

یک قول می تواند در حال تعلیق باشد، محقق شود یا رد شود

یک وعده در صورت تحقق یا رد شدن، حل می شود

then از آن برای بدست آوردن ارزش برآورده شده یک Promise استفاده کنید

از catch برای رسیدگی به خطاها استفاده کنید

finally برای اجرای منطق پاکسازی که در صورت موفقیت یا خطا به آن نیاز دارید، استفاده کنید

زنجیره ای با هم قول می دهد که وظایف ناهمزمان را به ترتیب انجام دهند

از Promise.all برای دریافت قولی استفاده کنید که در صورت تحقق همه وعده‌های داده شده محقق می‌شود، یا زمانی که یکی از آن وعده‌ها رد می‌شود، رد می‌شود.

Promise.allSettled برای دریافت قولی استفاده کنید که زمانی محقق می شود که همه وعده های داده شده محقق شوند یا رد شوند.

Promise.race برای دریافت قولی استفاده کنید که در صورت تحقق یا رد شدن اولین وعده داده شده محقق یا رد شود.

از Promise.any برای دریافت وعده ای استفاده کنید که با تحقق اولین وعده از وعده های داده شده محقق شود.

از کلمه کلیدی await برای منتظر ماندن برای ارزش تحقق یک Promise استفاده کنید

هنگام استفاده از کلمه کلیدی await از یک بلوک try-catch برای رسیدگی به خطاها استفاده کنید

تابعی که از await در داخل آن استفاده می کند باید از کلمه کلیدی async استفاده کند

از شما برای خواندن این شیرجه عمیق در Promises متشکریم. امیدوارم چیز جدیدی یاد گرفته باشید!

خبرکاو