Promiseها در جاوااسکریپت – بخش اول

جاوااسکریپت از جمله زبان‌هایی است که اتفاقات غیر همزمان (asynchronous) در آن معمول و متداول است. مثلا وقتی یک درخواست به سرور می‌فرستید جاوااسکریپت منتظر نمی‌ماند تا پاسخ آن دریافت شود. بلکه به اجرای خط به خط برنامه ادامه می‌دهد. اما این همیشه مطلوب نیست. بعضی اوقات لازم است تا از جاوااسکریپت قول بگیریم که بعد از مشخص شدن تکلیف اجرای یک دستور غیر همزمان کاری را انجام دهد. این‌جاست که راه Promiseها به قضیه باز می‌شود. دنیای برنامه‌نویسی غیر هم‌زمان دنیای مرموز و گاها دشواری است. پس بیایید یکی از بهترین مقالات را در این زمینه مرور کنیم. لینک اصلی این مقاله را در انتهای مطلب برایتان می‌گذارم تا در صورت تمایل سری به آن بزنید.

در حال حاضر یکی از این سه دسته هستید:

  • همه جا صحبت از Promiseها است. اما شما دقیقا نمی‌دانید چه خبر است! برای همین می‌خواهید آب شوید و بروید زیر زمین! اگر این‌طور است نگران نباشید. درک اهمیت این موضوع برای من هم زمان‌بر بوده است. برای شروع با من همراه شوید.
  • با Promiseها قبل از وارد شدن به جاوااسکریپت کار کرده‌اید. با کتابخانه‌های ریز و درشت! حالا می‌خواهید بدانید نسخه‌ی رسمی چه شکلی است. برای شما هم مطالبی داریم.
  • در کار کردن با Promiseها خبره‌اید. با غرور و سربلندی به تازه‌کارها نگاه می‌کنید و با خودتان حال می‌کنید. مطالب حرفه‌ای برای شما هم داریم. دانش خودتان را مرور کنید.
Promiseها در جاوا اسکریپت

مفهوم Promiseها در جاوااسکریپت

جاوااسکریپت تک‌رشته (Single threaded) است. یعنی دو تکه کد را هم‌زمان اجرا نمی‌کند. پس کدها یکی پس از دیگری اجرا می‌شوند. بنابراین وقتی که جاوااسکریپت مشغول کاری است، تغییر در استایل‌ها، انجام درخواست‌های کاربران، پردازش المان‌های صفحه، ارسال فرم‌ها و … آن کار قبلی جاوااسکریپت را به تعویق می‌اندازد. در حالی که ما به عنوان یک انسان تک رشته نیستیم. می‌توانیم با چند انگشت تایپ کنیم یا هم‌زمان که رانندگی می‌کنیم صحبت کنیم. تنها مورد تک رشته شدن ما وقتی است که عطسه می‌کنیم! خیلی بد است! مخصوصا وقتی داریم حین رانندگی صحبت می‌کنیم. کد آغشته به عطسه هم به همان مقدار بد است!

احتمالا برای خلاصی از این مساله از eventها و callbackها استفاده می‌کنید:

var img1 = document.querySelector('.img-1');

img1.addEventListener('load', function() {
  // Eyvalll aks load shod!
});

img1.addEventListener('error', function() {
  // Poof be fana raft!
});

این کد بدی نیست. ما عکس را می‌گیریم، چند listener تعریف می‌کنیم و جاوااسکریپت به کارهای پردازشی خود ادامه می‌دهد تا نهایتا یکی از listenerها فراخوانی شود. اما در این‌جا ممکن است قبل از تعریف listenerها اتفاق مورد نظر ما (در اینجا لود شدن عکس) اتفاق بیفتد. بنابراین باید از خصوصیت complete عکس‌ها استفاده کرده و به شکل زیر کد را تغییر دهیم:

var img1 = document.querySelector('.img-1');

function loaded() {
  // Eyvalll aks load shod!
}

if (img1.complete) {
  loaded();
}
else {
  img1.addEventListener('load', loaded);
}

img1.addEventListener('error', function() {
  // Poof be fana raft!
});

این کد، عکس‌هایی که قبل از تعریف listener خطا (خط ۱۴)، دچار خطا شوند را تشخیص نمی‌دهد. متاسفانه DOM نیز راهی برای تشخیص آن در اختیار ما قرار نداده است. تازه این ماجرا برای لود یک عکس است. حالا اگر بخواهیم مجموعه‌ای از عکس‌ها را لود کنیم قضیه پیچیده‌تر هم می‌شود.

Eventها همیشه بهترین راه‌حل نیستند!

Eventها وقتی عالی هستند که بخواهیم یک اتفاق را بارها تکرار کنیم. مثلا کلیدی را فشار دهیم یا کلیک کنیم. در این حالت خیلی اهمیت نمی‌دهیم که قبل از تعریف listener چه اتفاقی افتاده است. در مورد موفقیت یا عدم موفقیت یک رویداد غیر همزمان چیزی شبیه به کد زیر مطلوب ما است:

img1.ageLoadShodInoFarakhaniKon(function() {
  // load shod
}).ammaAgeLoadNashod(function() {
  // load nashod
});

// va…
ageHameyeInaLoadShodan([img1, img2]).inoSedaBezan(function() {
  // Hamegi load shodan!
}).ammaAgeYeMoshkeliPishOmad(function() {
  // Yeki ya chanta az aksa load nashod!
});

این دقیقا چیزی هست که Promiseها انجام می‌دهند؛ البته با نامگذاری بهتر!

در اصل Promiseها شباهت‌های زیادی به Eventها دارند. البته با تفاوت‌های زیر:

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

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

چند اصطلاح در مورد Promiseها

یک Promise می‌تواند یکی از حالت‌های زیر را داشته باشد:

  • Fullfiled: زمانی که عمل Promise با موفقیت به پایان برسد، Promise در این حالت قرار می‌گیرد.
  • Rejected: زمانی که عمل Promise به شکست بیانجامد، Promise در این حالت قرار می‌گیرد.
  • Pending: عمل Promise هنوز تکمیل نشده است.
  • Settled: عمل Promise تکمیل شده است. یعنی چه موفق باشد یا این‌که شکست بخورد Promise بعد از تکمیل شدن Settled محسوب می‌شود.

همچنین به Objectهایی که شبه-Promise باشند، یعنی دارای متد then باشند، tenable گفته می‌شود؛ به این معنی که این Objectها قابلیت تبدیل به Promiseهای واقعی را دارند.

قبل از این‌که Promiseها به صورت رسمی به جاوااسکریپت راه پیدا کنند، کتابخانه‌هایی برای اضافه کردن این قابلیت به کد وجود داشت که از جمله‌ی این‌ها عبارتند از:

همه‌ی کتابخانه‌های بالا از یک رفتار استاندارد شده به اسم +Promises/A استفاده می‌کنند. اگر کاربر jQuery باشید، حتما می‌دانید که این کتابخانه نیز از مفهوم مشابهی به اسم  Deferred استفاده می‌کند که البته با استاندارد +Promise/A سازگار نیست و باعث می‌شود اندکی متفاوت و کارایی آن اندکی کم‌تر باشد. با وجود این‌که همه‌ی کتابخانه‌های Promise از مفهوم استاندارد شده‌ی مشابهی استفاده می‌کنند اما نحوه‌ی کاربرد آن‌ها با یکدیگر متفاوت است. Promiseها در جاوااسکریپت از لحاظ نحوه‌ی کاربرد شبیه به کتابخانه‌ی RSVP.js است.

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

دیدگاهتان را بنویسید

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