آموزش Promise در JavaScript | از صفر تا پروژه واقعی + مثال عملی

آموزش Promise در JavaScript | از صفر تا پروژه واقعی + مثال عملی

  • سطح : پیشرفته
  • مطالعه : 22 دقیقه
  • 96

Promise-CTA

اگر چند سال پیش برنامه‌نویسی وب را شروع کرده باشید، احتمالاً اولین باری که با کدهای ناهمزمان (Asynchronous) روبه‌رو شدید، احساس کردید همه چیز کمی عجیب است.

چرا بعضی از دستورات بلافاصله اجرا نمی‌شوند؟

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

و مهم‌تر از همه، Promise دقیقاً چه مشکلی را حل می‌کند؟

بسیاری از آموزش‌ها از همان ابتدا سراغ سینتکس new Promise() یا متدهای then() و catch() می‌روند؛ اما اگر دلیل به وجود آمدن Promise را ندانید، حفظ کردن این دستورات کمکی به درک واقعی موضوع نخواهد کرد.

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

در پایان این آموزش، نه‌تنها می‌توانید Promise را بنویسید، بلکه رفتار آن را نیز به‌خوبی درک خواهید کرد و آماده یادگیری Async/Await خواهید بود.

داستانی که Promise را برای همیشه در ذهنتان ماندگار می‌کند

فرض کنید وارد یک رستوران شلوغ شده‌اید.

بعد از نشستن پشت میز، گارسون سفارش شما را یادداشت می‌کند و به آشپزخانه می‌فرستد.

حالا دو اتفاق ممکن است رخ دهد.

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

اما چیزی که در دنیای واقعی اتفاق می‌افتد، کاملاً متفاوت است.

شما سفارش را ثبت می‌کنید، گارسون آن را به آشپزخانه تحویل می‌دهد و تا زمانی که غذا آماده شود، می‌توانید با دوستانتان صحبت کنید، نوشیدنی سفارش دهید یا حتی منوی غذا را دوباره نگاه کنید.

وقتی غذا آماده شد، گارسون آن را روی میز شما قرار می‌دهد.

Promise نیز دقیقاً همین رفتار را در JavaScript پیاده‌سازی می‌کند.

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

نکته

NOTE

Promise باعث سریع‌تر شدن اینترنت یا سرور نمی‌شود.

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

مثال رستوران برای درک Promise در JavaScript

چرا JavaScript منتظر عملیات‌های زمان‌بر نمی‌ماند؟

شاید برایتان سؤال باشد که چرا JavaScript مانند بعضی زبان‌های دیگر، هنگام دریافت اطلاعات از سرور یا دانلود فایل، منتظر پایان عملیات نمی‌ماند.

دلیل این موضوع به معماری JavaScript برمی‌گردد.

JavaScript یک زبان Single Thread است؛ یعنی در هر لحظه فقط یک دستور را اجرا می‌کند.

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

  • دکمه‌های صفحه دیگر کار نمی‌کردند.
  • اسکرول صفحه متوقف می‌شد.
  • انیمیشن‌ها از حرکت می‌ایستادند.
  • کاربر تصور می‌کرد سایت هنگ کرده است.

برای جلوگیری از این مشکل، JavaScript عملیات‌های زمان‌بر را به محیط اجرا (Runtime) می‌سپارد و خودش به اجرای سایر دستورات ادامه می‌دهد.

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

به همین دلیل است که صفحات وب حتی هنگام بارگذاری اطلاعات نیز همچنان روان و قابل استفاده باقی می‌مانند.

JavaScript Asynchronous Flow

Promise چیست؟

Promise یک شیء (Object) در JavaScript است که نتیجه یک عملیات ناهمزمان را مدیریت می‌کند.به زبان ساده، Promise یک قول است.

قولی که می‌گوید:

«من این عملیات را انجام می‌دهم و بعداً نتیجه آن را در اختیار تو قرار می‌دهم.»

این نتیجه می‌تواند موفقیت‌آمیز باشد یا با خطا مواجه شود.

به همین دلیل، Promise همیشه در یکی از سه وضعیت زیر قرار دارد:

  • Pending (در انتظار)
  • Fulfilled (موفق)
  • Rejected (ناموفق)

در بخش بعدی با این سه وضعیت به‌طور کامل آشنا خواهیم شد.

Callback چیست و قبل از Promise چه مشکلی وجود داشت؟

قبل از معرفی Promise، برنامه‌نویسان جاوااسکریپت برای مدیریت عملیات‌های ناهمزمان از Callback استفاده می‌کردند.

Callback در واقع یک تابع است که به عنوان آرگومان به تابع دیگری ارسال می‌شود تا پس از پایان یک عملیات اجرا شود.

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


  function getUser(callback) {
    setTimeout(() => {
      callback({
        id: 1,
        name: "Vahid",
      });
    }, 2000);
  }

  getUser(function (user) {
    console.log(user);
  });

در این مثال، تابع getUser پس از دو ثانیه، تابع Callback را اجرا می‌کند و اطلاعات کاربر را در اختیار آن قرار می‌دهد.

در پروژه‌های کوچک این روش کاملاً مناسب است.

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

Callback در JavaScript

Callback Hell چیست؟

فرض کنید برای نمایش صفحه پروفایل کاربر باید مراحل زیر انجام شوند.

  • دریافت اطلاعات کاربر
  • دریافت لیست سفارش‌ها
  • دریافت آدرس‌ها
  • دریافت پیام‌ها
  • دریافت اعلان‌ها

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

اما هرچه پروژه بزرگ‌تر شود، خواندن و نگهداری آن سخت‌تر خواهد شد.


   getUser(function (user) {
    getOrders(user.id, function (orders) {
      getAddresses(user.id, function (addresses) {
        getMessages(user.id, function (messages) {
          console.log("Done");
        });
      });
    });
  });

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

برنامه‌نویسان گاهی به شوخی به آن Pyramid of Doom نیز می‌گویند؛ زیرا ساختار کد شبیه یک هرم می‌شود.
Callback Hell در JavaScript

چرا Callback Hell یک مشکل جدی بود؟

شاید در مثال قبلی کد چندان پیچیده به نظر نرسد.

اما تصور کنید پروژه‌ای شامل هزاران خط کد داشته باشید.

در چنین شرایطی Callback Hell مشکلات زیادی ایجاد می‌کند.

  • خوانایی کد به شدت کاهش پیدا می‌کند.
  • پیدا کردن خطاها دشوار می‌شود.
  • مدیریت خطاها پیچیده‌تر خواهد شد.
  • نگهداری پروژه زمان بیشتری می‌برد.
  • اضافه کردن قابلیت‌های جدید سخت‌تر می‌شود.

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

نتیجه این تلاش، معرفی Promise بود.

Promise چگونه این مشکل را حل کرد؟

Promise همان عملیات قبلی را به شکلی کاملاً متفاوت مدیریت می‌کند.

به جای اینکه توابع را داخل یکدیگر قرار دهیم، هر عملیات نتیجه خود را به عملیات بعدی تحویل می‌دهد.

به این روش Promise Chaining گفته می‌شود.

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

به جای ساختار هرم‌مانند Callback Hell، اکنون کدها به صورت زنجیره‌ای نوشته می‌شوند.

در ادامه، اولین Promise خود را ایجاد می‌کنیم و قدم‌به‌قدم با ساختار آن آشنا می‌شویم.
Promise Chaining در JavaScript

سه وضعیت Promise

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

۱. Pending

در این وضعیت، عملیات هنوز در حال انجام است و نتیجه نهایی مشخص نشده است.

برای مثال، زمانی که درخواستی به سرور ارسال می‌کنید، تا قبل از دریافت پاسخ، Promise در وضعیت Pending قرار دارد.

۲. Fulfilled

اگر عملیات با موفقیت به پایان برسد، Promise وارد وضعیت Fulfilled می‌شود.

در این حالت می‌توانید نتیجه را با استفاده از then() دریافت کنید.

۳. Rejected

اگر هنگام اجرای عملیات خطایی رخ دهد، Promise وارد وضعیت Rejected خواهد شد.

در این حالت، مدیریت خطا معمولاً توسط catch() انجام می‌شود.

سه وضعیت Promise در JavaScript

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

ساخت اولین Promise در JavaScript

تا اینجا با مفهوم Promise و دلیل به وجود آمدن آن آشنا شدیم.

اکنون زمان آن رسیده است که اولین Promise خود را بسازیم.

ساختار کلی Promise بسیار ساده است.

برای ایجاد یک Promise از کلمه کلیدی new Promise() استفاده می‌کنیم.

داخل آن نیز تابعی قرار می‌گیرد که دو پارامتر مهم در اختیار ما قرار می‌دهد:

  • resolve
  • reject

ساختار کلی به صورت زیر است.


  const promise = new Promise((resolve, reject) => {
     // عملیات زمان‌بر 
     });

ممکن است در نگاه اول این کد کمی عجیب به نظر برسد، اما نگران نباشید؛ در ادامه تمام بخش‌های آن را به زبان ساده بررسی می‌کنیم.

Executor Function چیست؟

تابعی که داخل new Promise() قرار می‌گیرد، Executor Function نام دارد.

به محض اینکه Promise ساخته می‌شود، این تابع بلافاصله اجرا می‌شود.

این موضوع یکی از نکاتی است که بسیاری از افراد در ابتدای یادگیری Promise از آن اطلاع ندارند.

به عبارت دیگر، نیازی نیست Executor را خودتان فراخوانی کنید؛ جاوااسکریپت هنگام ایجاد Promise این کار را انجام می‌دهد.
ساختار Promise در JavaScript

resolve() چیست؟

تابع resolve() زمانی فراخوانی می‌شود که عملیات با موفقیت به پایان برسد.
هر مقداری که به resolve() ارسال کنید، در ادامه توسط then() قابل دریافت خواهد بود.

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


  const promise = new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve("اطلاعات با موفقیت دریافت شد.");
    }, 3000);
  });

در این مثال، سه ثانیه بعد تابع resolve() اجرا می‌شود و Promise وارد وضعیت Fulfilled خواهد شد.

reject() چیست؟

گاهی عملیات با موفقیت انجام نمی‌شود.

برای مثال:

  • اینترنت کاربر قطع شده است.
  • اینترنت کاربر قطع شده است.
  • سرور پاسخ نمی‌دهد.
  • فایل موردنظر وجود ندارد.
  • کاربر مجوز لازم را ندارد.

در چنین شرایطی باید از reject() استفاده کنیم.


  const promise = new Promise((resolve, reject) => {
    setTimeout(() => {
      reject("ارتباط با سرور برقرار نشد.");
    }, 3000);
  });

در این حالت Promise وارد وضعیت Rejected می‌شود.
تفاوت resolve و reject

اولین Promise کامل

اکنون یک مثال واقعی‌تر می‌نویسیم که هم موفقیت و هم خطا را مدیریت کند.


  const getUser = new Promise((resolve, reject) => {
    const success = true;
    setTimeout(() => {
      if (success) {
        resolve({ id: 1, name: "Vahid" });
      } else {
        reject("خطا در دریافت اطلاعات کاربر.");
      }
    }, 2000);
  });

در این مثال، متغیر success مشخص می‌کند که عملیات موفق بوده است یا خیر.

اگر مقدار آن true باشد، Promise با موفقیت پایان پیدا می‌کند.

در غیر این صورت، Promise با خطا مواجه خواهد شد.

بیایید این کد را خط به خط تحلیل کنیم

یکی از بهترین روش‌های یادگیری Promise این است که بدانیم مرورگر دقیقاً چه اتفاقی را در پشت صحنه انجام می‌دهد.

مراحل اجرای کد بالا به ترتیب زیر است.

  • ابتدا Promise ساخته می‌شود.
  • Executor Function بلافاصله اجرا می‌شود.
  • تابع setTimeout زمان‌سنج را فعال می‌کند.
  • Promise وارد وضعیت Pending می‌شود.
  • پس از دو ثانیه شرط بررسی می‌شود.
  • اگر عملیات موفق باشد، resolve() اجرا می‌شود.
  • اگر عملیات شکست بخورد، reject() اجرا می‌شود.
  • Promise از وضعیت Pending خارج می‌شود.
  • نتیجه برای then() یا catch() آماده خواهد شد.

اگر این روند را به خوبی درک کنید، بخش بزرگی از مسیر یادگیری Promise را پشت سر گذاشته‌اید.
چرخه اجرای Promise

دریافت نتیجه Promise با then()

تا اینجا Promise را ایجاد کردیم.
>اما هنوز نتیجه آن را دریافت نکرده‌ایم.

برای دریافت مقدار ارسال‌شده توسط resolve() از متد then() استفاده می‌کنیم.


  getUser.then((user) => {
    console.log(user);
  });

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

در مثال قبلی، مقدار user برابر خواهد بود با:


{ id: 1, name: "Vahid" }

چرا then() این‌قدر مهم است؟

then() فقط برای نمایش نتیجه نیست.
در واقع، هر then() یک Promise جدید برمی‌گرداند.

همین ویژگی باعث می‌شود بتوانیم چندین عملیات را پشت سر هم اجرا کنیم؛ چیزی که در بخش بعد با عنوان Promise Chaining یاد خواهیم گرفت.

متد then در Promise
>>در بخش بعد، یاد می‌گیریم چگونه خطاها را با catch() مدیریت کنیم، از finally() استفاده کنیم و چندین Promise را به صورت زنجیره‌ای اجرا کنیم.

مدیریت خطاها با catch()

در دنیای واقعی، همه چیز همیشه مطابق انتظار پیش نمی‌رود.

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

به همین دلیل، یکی از مهم‌ترین ویژگی‌های Promise، امکان مدیریت خطاها است.

برای این کار از متد catch() استفاده می‌کنیم.

هر زمان که Promise با استفاده از reject() خاتمه پیدا کند یا در یکی از مراحل اجرای Promise خطایی رخ دهد، کنترل برنامه به catch() منتقل خواهد شد.

یک مثال ساده


 const getUser = new Promise((resolve, reject) => {
    const success = false;
    setTimeout(() => {
      if (success) {
        resolve("اطلاعات دریافت شد.");
      } else {
        reject("ارتباط با سرور برقرار نشد.");
      }
    }, 2000);
  });

  getUser
    .then((result) => {
      console.log(result);
    })
    .catch((error) => {
      console.log(error);
    });

در این مثال مقدار success برابر false است.

بنابراین Promise وارد وضعیت Rejected شده و پیام خطا توسط catch() نمایش داده می‌شود.

استفاده از catch در Promise

آیا catch() فقط برای reject() است؟

خیر.

این یکی از نکاتی است که بسیاری از افراد تازه‌کار از آن اطلاع ندارند.

اگر داخل یکی از then()ها نیز خطایی رخ دهد، باز هم catch() آن را دریافت خواهد کرد.


 getUser
    .then((user) => {
      console.log(user.name);

      throw new Error("خطای دلخواه");
    })
    .catch((error) => {
      console.log(error.message);
    });

در نتیجه، catch() فقط مخصوص reject() نیست؛ بلکه تمام خطاهای ایجادشده در زنجیره Promise را مدیریت می‌کند.

finally() چیست؟

گاهی لازم نیست بدانیم عملیات موفق بوده یا با خطا مواجه شده است.

فقط می‌خواهیم بعد از پایان عملیات، کاری انجام دهیم.

برای این کار از finally() استفاده می‌کنیم.

نمونه‌های رایج:

مخفی کردن Loading
بستن Spinner
آزاد کردن منابع
فعال کردن دوباره دکمه‌ها
مثال :


fetch("/api/users")
    .then((response) => response.json())

    .then((data) => {
      console.log(data);
    })

    .catch((error) => {
      console.log(error);
    })

    .finally(() => {
      console.log("درخواست به پایان رسید.");
    });

در این مثال، بدون توجه به موفق یا ناموفق بودن درخواست، کد داخل finally() اجرا خواهد شد.
finally در Promise

Promise Chaining چیست؟

یکی از مهم‌ترین قابلیت‌های Promise، امکان اجرای چند عملیات به صورت زنجیره‌ای است.

فرض کنید ابتدا اطلاعات کاربر را دریافت می‌کنیم.

پس از آن باید سفارش‌های او را بخوانیم.

سپس اطلاعات پرداخت را دریافت کنیم.

هر مرحله به مرحله قبل وابسته است.

اگر از Callback استفاده کنیم، دوباره وارد Callback Hell خواهیم شد.

اما Promise این مشکل را با Promise Chaining حل می‌کند.

مثال:


  getUser()
    .then((user) => {
      return getOrders(user.id);
    })
    .then((orders) => {
      return getPayments(orders);
    })
    .then((payments) => {
      console.log(payments);
    })
    .catch((error) => {
      console.log(error);
    });

هر then() نتیجه خود را به then() بعدی ارسال می‌کند.

به همین دلیل، کد بسیار خواناتر و نگهداری آن ساده‌تر خواهد بود.
Promise Chaining در JavaScript

چرا باید داخل then() از return استفاده کنیم؟

یکی از رایج‌ترین اشتباهات برنامه‌نویسان، فراموش کردن return است.

مثال اشتباه:


  getUser()
    .then((user) => {
      getOrders(user.id);
    })
    .then((orders) => {
      console.log(orders);
    });

در این مثال، Promise دوم منتظر پایان getOrders() نمی‌ماند.

روش صحیح:


   getUser()
    .then((user) => {
      return getOrders(user.id);
    })
    .then((orders) => {
      console.log(orders);
    });

استفاده از return باعث می‌شود Promise بعدی منتظر پایان Promise قبلی بماند.

زنجیره Promise چگونه اجرا می‌شود؟

فرض کنید سه Promise پشت سر هم داریم.

  • دریافت اطلاعات کاربر
  • دریافت سفارش‌ها
  • دریافت فاکتورها

جاوااسکریپت ابتدا Promise اول را اجرا می‌کند.

پس از پایان موفقیت‌آمیز آن، Promise دوم آغاز می‌شود.

در نهایت Promise سوم اجرا خواهد شد.

اگر در هر مرحله خطایی رخ دهد، ادامه زنجیره متوقف شده و کنترل برنامه مستقیماً به catch() منتقل می‌شود.

به همین دلیل معمولاً فقط یک catch() در انتهای زنجیره کافی است.
ترتیب اجرای Promise Chaining

چه زمانی از Promise Chaining استفاده کنیم؟

Promise Chaining زمانی بهترین انتخاب است که هر مرحله به نتیجه مرحله قبل وابسته باشد.

برای مثال:

  • ورود کاربر
  • دریافت اطلاعات پروفایل
  • دریافت سفارش‌های کاربر
  • دریافت جزئیات هر سفارش

در چنین شرایطی اجرای مرحله بعد بدون نتیجه مرحله قبل امکان‌پذیر نیست.

اما اگر عملیات‌ها به هم وابسته نباشند، بهتر است از Promise.all() استفاده کنیم؛ موضوعی که در بخش بعد به طور کامل بررسی خواهیم کرد.

اجرای هم‌زمان چند Promise با Promise.all()

تا اینجا تمام مثال‌هایی که بررسی کردیم به صورت ترتیبی اجرا می‌شدند؛ یعنی هر Promise منتظر پایان Promise قبلی بود.
>اما همیشه این‌گونه نیست.

گاهی چند عملیات کاملاً مستقل از یکدیگر هستند و می‌توانند هم‌زمان اجرا شوند.

در چنین شرایطی، استفاده از Promise.all() بهترین انتخاب است.

یک مثال واقعی

فرض کنید کاربر وارد داشبورد سایت می‌شود.

برای نمایش کامل صفحه باید اطلاعات زیر دریافت شوند:

  • اطلاعات پروفایل
  • سفارش‌های اخیر
  • پیام‌های جدید
  • اعلان‌ها

هیچ‌کدام از این درخواست‌ها به دیگری وابسته نیست.

پس منطقی نیست آن‌ها را یکی‌یکی اجرا کنیم.

مثال:


  Promise.all([getProfile(), getOrders(), getMessages(), getNotifications()])
    .then((results) => {
      console.log(results);
    })
    .catch((error) => {
      console.log(error);
    });

در این مثال هر چهار درخواست به صورت هم‌زمان ارسال می‌شوند.

Promise.all تا زمانی که همه Promiseها به پایان نرسند، منتظر می‌ماند.

پس از آن، نتیجه همه آن‌ها را به صورت یک آرایه در اختیار شما قرار می‌دهد.

Promise.all-در-JavaScript.png

اگر یکی از Promiseها شکست بخورد چه می‌شود؟

این یکی از مهم‌ترین نکاتی است که باید درباره Promise.all بدانید.

فرض کنید چهار درخواست هم‌زمان ارسال کرده‌اید.

اگر حتی یکی از آن‌ها با خطا مواجه شود:

  • Promise.all متوقف می‌شود.
  • مستقیماً وارد catch خواهد شد.
  • نتیجه سایر Promiseها بازگردانده نمی‌شود.

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

Promise.allSettled چیست؟

گاهی شکست خوردن یک درخواست، نباید باعث از بین رفتن نتایج سایر درخواست‌ها شود.

برای این شرایط، JavaScript متد Promise.allSettled() را معرفی کرده است.

این متد منتظر پایان تمام Promiseها می‌ماند؛ حتی اگر برخی از آن‌ها با خطا مواجه شوند.

مثال:


   Promise.allSettled([getProfile(), getOrders(), getMessages()]).then(
    (results) => {
      console.log(results);
    },
  );

خروجی این متد شامل وضعیت هر Promise خواهد بود.

برای مثال:

  • fulfilled
  • rejected

در نتیجه می‌توانید برای هر درخواست به صورت جداگانه تصمیم بگیرید.
Promise.allSettled در JavaScript

Promise.race چیست؟

گاهی فقط به اولین نتیجه نیاز داریم.

در چنین شرایطی از Promise.race() استفاده می‌کنیم.

این متد منتظر اولین Promise می‌ماند؛ چه موفق شود و چه با خطا پایان یابد.

مثال :


  Promise.race([serverOne(), serverTwo(), serverThree()])

    .then((result) => {
      console.log(result);
    });

فرض کنید سه سرور مختلف دارید.

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

Promise.race در JavaScript

Promise.any چیست؟

در نگاه اول ممکن است Promise.any شبیه Promise.race به نظر برسد، اما تفاوت مهمی بین آن‌ها وجود دارد.

Promise.any اولین Promise موفق را بازمی‌گرداند.

اگر اولین Promise با خطا مواجه شود، منتظر Promise بعدی خواهد ماند.

مثال:


  Promise.any([
      apiOne(),
      apiTwo(),
      apiThree()
  ])
  .then(result => {
      console.log(result);
  });

اگر API اول خطا بدهد اما API دوم موفق باشد، نتیجه API دوم بازگردانده خواهد شد.

Promise.any در JavaScript

Promise یا Async/Await؟

این سؤال تقریباً برای تمام برنامه‌نویسان پیش می‌آید.

کدام بهتر است؟

Promise یا Async/Await؟

پاسخ کوتاه این است:

هیچ‌کدام جایگزین دیگری نیستند.

در واقع Async/Await روی Promise ساخته شده است.

یعنی هر زمان از Async/Await استفاده می‌کنید، در پشت صحنه همچنان Promise در حال اجراست.

تفاوت اصلی فقط در نحوه نوشتن کد است.

چه زمانی از Promise استفاده کنیم؟

در پروژه‌های واقعی معمولاً در این شرایط مستقیماً با Promise سروکار دارید:

  • Fetch API
  • Axios
  • Firebase
  • IndexedDB
  • Cache API
  • Service Worker
  • Web Bluetooth
  • بسیاری از کتابخانه‌های مدرن JavaScript

به همین دلیل، حتی اگر بیشتر از Async/Await استفاده کنید، درک Promise همچنان یک مهارت ضروری محسوب می‌شود.

در بخش بعد، با مهم‌ترین Best Practiceها، اشتباهات رایج برنامه‌نویسان و نکات حرفه‌ای کار با Promise آشنا خواهیم شد.

بهترین روش‌های استفاده از Promise (Best Practices)

یاد گرفتن سینتکس Promise تنها بخشی از مسیر است.

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

در ادامه، مهم‌ترین Best Practiceهایی را بررسی می‌کنیم که در پروژه‌های واقعی بارها با آن‌ها روبه‌رو خواهید شد.

۱. همیشه خطاها را مدیریت کنید

یکی از رایج‌ترین اشتباهات، نوشتن Promise بدون catch() است.

نمونه اشتباه:


  fetch("/api/users")
    .then((response) => response.json())
    .then((data) => console.log(data));

اگر ارتباط با سرور قطع شود یا درخواست با خطا مواجه شود، هیچ بخشی مسئول مدیریت آن نخواهد بود.

روش صحیح:


fetch("/api/users")
    .then((response) => response.json())
    .then((data) => console.log(data))
    .catch((error) => console.error(error));

۲. Promise را فقط برای عملیات‌های ناهمزمان استفاده کنید

گاهی دیده می‌شود که برنامه‌نویسان حتی برای محاسبات ساده نیز Promise ایجاد می‌کنند.

این کار فقط باعث پیچیده‌تر شدن کد خواهد شد.

Promise زمانی معنا پیدا می‌کند که عملیات زمان‌بر باشد؛ مانند:

  • درخواست HTTP
  • خواندن فایل
  • ارتباط با پایگاه داده
  • دریافت موقعیت مکانی کاربر
  • دسترسی به دوربین

۳. برای عملیات‌های مستقل از Promise.all استفاده کنید

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

۴. Promise داخل Promise نسازید

اگر داخل then() دوباره Promise جدید ایجاد کنید، کد دوباره به سمت Callback Hell حرکت خواهد کرد.

در بیشتر مواقع فقط کافی است Promise را return کنید.

۵. وقتی کد طولانی شد، از Async/Await استفاده کنید

اگر چندین Promise پشت سر هم دارید، معمولاً Async/Await خواناتر خواهد بود.

البته فراموش نکنید که Async/Await خودش بر پایه Promise ساخته شده است.

Best Practices Promise در JavaScript

آیا می‌دانستید؟

بسیاری از برنامه‌نویسان تصور می‌کنند در پروژه‌های واقعی دائماً با new Promise() سروکار دارند.

در حالی که واقعیت کاملاً متفاوت است.

در اغلب پروژه‌ها Promise توسط کتابخانه‌ها یا APIها برای شما ساخته می‌شود.

برای مثال:

  • Fetch API
  • Axios
  • Firebase
  • IndexedDB
  • Cache API

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

اشتباهات رایج هنگام کار با Promise

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

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

❌ فراموش کردن catch()

بدون مدیریت خطا، پیدا کردن علت مشکل در پروژه‌های بزرگ بسیار دشوار خواهد شد.

همیشه زنجیره Promise را با catch() پایان دهید.

❌ فراموش کردن return

یکی از رایج‌ترین خطاها.

نمونه اشتباه:


  getUser()
    .then((user) => {
      getOrders(user.id);
    })

    .then((orders) => {
      console.log(orders);
    });

در این مثال Promise دوم منتظر پایان Promise اول نمی‌ماند.

روش صحیح:


   getUser()
    .then((user) => {
      return getOrders(user.id);
    })

    .then((orders) => {
      console.log(orders);
    });

❌ استفاده از Promise برای همه چیز

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

❌ استفاده اشتباه از Promise.all

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

در چنین شرایطی Promise.allSettled انتخاب مناسب‌تری است.

❌ نادیده گرفتن finally

بسیاری از برنامه‌نویسان برای مخفی کردن Loading از then() استفاده می‌کنند.

در حالی که اگر درخواست با خطا مواجه شود، Loading هرگز مخفی نخواهد شد.

برای چنین کارهایی همیشه از finally() استفاده کنید.

 

تجربه Promise در پروژه‌های واقعی

تا اینجا بیشتر مثال‌های آموزشی را بررسی کردیم.

اما Promise در پروژه‌های واقعی دقیقاً کجا استفاده می‌شود؟

تقریباً در تمام پروژه‌های مدرن JavaScript با Promise سروکار خواهید داشت.

برای مثال:

  • پنل مدیریت
  • دریافت اطلاعات داشبورد
  • دریافت لیست کاربران
  • بارگذاری اعلان‌ها
  • فروشگاه اینترنتی
  • دریافت محصولات
  • ثبت سفارش
  • بررسی وضعیت پرداخت
  • دریافت اطلاعات کاربر
  • شبکه‌های اجتماعی
  • بارگذاری پست‌ها
  • دریافت کامنت‌ها
  • ارسال پیام
  • آپلود تصویر
  • پروژه‌های لاراول

اگر Backend پروژه با Laravel نوشته شده باشد، تقریباً تمام درخواست‌هایی که توسط Fetch یا Axios ارسال می‌شوند، با Promise مدیریت خواهند شد.

به همین دلیل، یادگیری Promise برای توسعه‌دهندگان Laravel نیز یک مهارت ضروری محسوب می‌شود.

آیا باید Promise را حفظ کنیم؟

خیر.

هدف این مقاله حفظ کردن متدها یا سینتکس نیست.

آنچه باید یاد بگیرید، طرز فکر Promise است.

اگر بدانید Promise چه مشکلی را حل می‌کند، یادگیری Async/Await، Fetch API، Axios و بسیاری از کتابخانه‌های دیگر برایتان بسیار ساده‌تر خواهد شد.

جمع‌بندی قبل از بخش پایانی

تا اینجا یاد گرفتیم:

  • Promise چیست.
  • چرا به وجود آمد.
  • Callback Hell چیست.
  • Promise چگونه آن را حل می‌کند.
  • سه وضعیت Promise چیست.
  • resolve و reject چه کاری انجام می‌دهند.
  • then، catch و finally چگونه کار می‌کنند.
  • Promise Chaining چیست.
  • Promise.all، Promise.allSettled، Promise.race و Promise.any چه تفاوتی دارند.
  • مهم‌ترین Best Practiceها و اشتباهات رایج کدام‌اند.

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

جمع‌بندی

تا اینجا قدم‌به‌قدم با Promise آشنا شدیم.

ابتدا دلیل به وجود آمدن آن را بررسی کردیم، سپس با Callback Hell آشنا شدیم و دیدیم Promise چگونه این مشکل را حل می‌کند.

بعد از آن، ساخت اولین Promise، متدهای then()، catch() و finally() را یاد گرفتیم و با ابزارهای قدرتمندی مانند Promise.all()، Promise.allSettled()، Promise.race() و Promise.any آشنا شدیم.

در نهایت نیز بهترین روش‌های استفاده از Promise و اشتباهات رایج برنامه‌نویسان را بررسی کردیم.

اگر تمام مثال‌های این مقاله را اجرا کرده باشید، اکنون درک بسیار خوبی از Promise دارید و آماده هستید تا سراغ Async/Await بروید.

چه زمانی از Promise استفاده کنیم؟

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

برای مثال:

  • دریافت اطلاعات از API
  • ارسال فرم‌ها
  • آپلود فایل
  • دانلود اطلاعات
  • ارتباط با Firebase
  • استفاده از Fetch API
  • استفاده از Axios
  • ارتباط با IndexedDB
  • بسیاری از کتابخانه‌های جاوااسکریپت

به همین دلیل، Promise یکی از مهم‌ترین مفاهیمی است که هر توسعه‌دهنده JavaScript باید آن را به خوبی درک کند.

چک‌لیست یادگیری Promise

قبل از اینکه این مقاله را ببندید، بررسی کنید که آیا می‌توانید به سؤال‌های زیر پاسخ دهید یا خیر.

  • Promise چیست؟
  • چرا Promise ایجاد شد؟
  • Callback Hell چیست؟
  • تفاوت resolve و reject چیست؟
  • تفاوت then و catch چیست؟
  • finally چه زمانی اجرا می‌شود؟
  • Promise Chaining چیست؟
  • تفاوت Promise.all و Promise.allSettled چیست؟
  • Promise.race و Promise.any چه تفاوتی دارند؟
  • چه زمانی باید از Async/Await استفاده کنیم؟

اگر پاسخ بیشتر این سؤال‌ها را می‌دانید، یعنی مفاهیم اصلی Promise را یاد گرفته‌اید.

قدم بعدی چیست؟

Promise پایان مسیر یادگیری عملیات‌های ناهمزمان نیست.

در واقع، این نقطه شروع است.

بعد از یادگیری Promise پیشنهاد می‌کنیم این مباحث را به ترتیب مطالعه کنید:

  • Async/Await
  • Fetch API
  • مدیریت خطاها با try…catch
  • ارسال درخواست‌های HTTP
  • Axios
  • AbortController
  • مدیریت درخواست‌های هم‌زمان
  • طراحی APIهای REST

این ترتیب باعث می‌شود مفاهیم به صورت مرحله‌به‌مرحله روی یکدیگر ساخته شوند.
نقشه راه یادگیری Promise و Async Await

[link url=”https://cafeamoozesh.com/javascript-async-await-tutorial” text=”اگر اکنون با Promise کاملاً آشنا شده‌اید، پیشنهاد می‌کنیم در ادامه مقاله آموزش Async/Await در JavaScript را مطالعه کنید تا با روش مدرن‌تر مدیریت عملیات‌های ناهمزمان نیز آشنا شوید.”]


پرسش‌های متداول (FAQ)

Promise چیست؟

Promise یک شیء در JavaScript است که نتیجه یک عملیات ناهمزمان را مدیریت می‌کند و می‌تواند در سه وضعیت Pending، Fulfilled یا Rejected قرار داشته باشد.


آیا Async/Await جایگزین Promise شده است؟

خیر.

Async/Await جایگزین Promise نیست؛ بلکه یک روش خواناتر برای کار با Promise است و در پشت صحنه همچنان از Promise استفاده می‌کند.


تفاوت Promise.all و Promise.allSettled چیست؟

در Promise.all اگر یکی از Promiseها با خطا مواجه شود، کل عملیات متوقف می‌شود.

اما Promise.allSettled منتظر پایان تمام Promiseها می‌ماند و نتیجه هر کدام را جداگانه برمی‌گرداند.


آیا هنوز هم باید Promise را یاد بگیریم؟

بله.

حتی اگر بیشتر از Async/Await استفاده کنید، دانستن Promise ضروری است؛ زیرا تقریباً تمام APIها و کتابخانه‌های مدرن جاوااسکریپت بر پایه Promise ساخته شده‌اند.


آیا Promise باعث افزایش سرعت برنامه می‌شود؟

خیر.

Promise سرعت اینترنت یا سرور را افزایش نمی‌دهد.

وظیفه آن مدیریت بهتر عملیات‌های ناهمزمان و افزایش خوانایی کد است.


منابع پیشنهادی برای مطالعه بیشتر

اگر می‌خواهید دانش خود را عمیق‌تر کنید، مطالعه منابع رسمی زیر را پیشنهاد می‌کنیم:

  • MDN Web Docs – Promise
  • JavaScript.info – Promise
  • ECMAScript Specification
  • MDN Web Docs – Fetch API
  • MDN Web Docs – Async/Await

این منابع مرجع اصلی جاوااسکریپت هستند و بسیاری از مثال‌های موجود در آن‌ها در پروژه‌های واقعی نیز کاربرد دارند.


سخن پایانی

Promise یکی از مهم‌ترین مفاهیمی است که هر برنامه‌نویس JavaScript باید آن را به خوبی درک کند.

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

توصیه می‌کنیم مثال‌های این مقاله را خودتان اجرا کنید، مقادیر را تغییر دهید و سناریوهای مختلف را امتحان کنید.

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

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

منابع بیشتر

READ MORE

منابع بیشتر

READ MORE
پرسش و پاسخ

اولین دیدگاه رو شما برای ما ارسال و کنید

کاربر گرامی : برای ارسال دیدگاه خود ابتدا باید در سایت لاگین نمایید.