۲۹ شهریور ۱۴۰۳

Techboy

اخبار و اطلاعات روز تکنولوژی

۵ روش برای استفاده از وعده های جاوا اسکریپت

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

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

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

برنامه نویسی ناهمزمان با وعده

در مثال زیر، ما از یک Promise برای مدیریت نتایج یک عملیات شبکه استفاده می‌کنیم. به جای برقراری تماس شبکه، فقط از یک مهلت زمانی استفاده می کنیم:


function fetchData() {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      const data = "This is the fetched data!";
      resolve(data); 
    }, ۲۰۰۰);
  });
}

const promise = fetchData();

promise.then((data) => {
  console.log("This will print second:", data);
});

console.log("This will print first.");

در این کد، یک تابع fetchData() تعریف می کنیم که یک Promise را برمی گرداند. متد را فراخوانی می کنیم و Promise را در متغیر promise نگه می داریم. سپس از روش Promise.then() برای مقابله با نتایج استفاده می کنیم.

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

اگر به داخل fetchData() نگاه کنید، خواهید دید که یک شی Promise را تعریف می کند و آن شی تابعی با دو آرگومان می گیرد: resolve و رد. اگر Promise موفق شود، resolve را فراخوانی می کند. اگر مشکلی وجود داشته باشد، reject را صدا می کند. در مورد ما، نتیجه تماس شبکه را با فراخوانی resolve و برگرداندن یک رشته شبیه‌سازی می‌کنیم.

اغلب اوقات، Promise را می‌بینید که به طور مستقیم با آن تماس گرفته می‌شود، مانند:


fetchData().then((data) => {
  console.log("This will print second:", data);
});

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


function fetchData() {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      if (Math.random() < 0.5) {
        reject("An error occurred while fetching data!");
      } else {
        const data = "This is the fetched data!";
        resolve(data);
      }
    }, ۲۰۰۰);
  });
}

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


fetchData().then((data) => {
  console.log("That was a good one:", data);
}).catch((error) => {
  console.log("That was an error:", error)
});

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

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

یکی از زیبایی های وعده ها این است که می توانید آنها را به هم زنجیر کنید. این به جلوگیری از تماس های عمیق تو در تو کمک می کند و مدیریت خطاهای ناهمزمان تودرتو را ساده می کند. (با نشان دادن یک تابع قدیمی جاوا اسکریپت-با آرگومان-تماس-بازخوانی، آب را گل آلود نمی کنم. حرف من را بپذیرید، که به هم می ریزد.)

با رها کردن تابع fetchData() ما به همان صورت، اجازه دهید یک تابع processData() اضافه کنیم. تابع processData() به نتایج fetchData() بستگی دارد. اکنون، می‌توانیم منطق پردازش را درون تماس برگشتی از fetchData() بپیچیم، اما وعده‌ها به ما اجازه می‌دهند کاری بسیار تمیزتر انجام دهیم:


function processData(data) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      const processedData = data + " - Processed";
      resolve(processedData);
    }, ۱۰۰۰);
  });
}

fetchData()
  .then((data) => {
    console.log("Fetched data:", data);
    return processData(data); 
  })
  .then((processedData) => {
    console.log("Processed data:", processedData);
  })
  .catch((error) => {
    console.error("Error:", error);
  });

اگر این کد را چندین بار اجرا کنید، متوجه خواهید شد که وقتی fetchData() موفق شد، هر دو روش then() به درستی فراخوانی می شوند. هنگامی که fetchData() خراب می شود، کل زنجیره اتصال کوتاه و انتهای catch()  فراخوانی می شود. این شبیه نحوه عملکرد بلوک‌های try/catch است.

اگر catch() را بعد از then() اول قرار دهیم، فقط مسئول خطاهای fetchData() خواهد بود. . در این مورد، catch() ما هر دو خطای fetchData() و processData() را کنترل خواهد کرد.

کلید اینجا این است که کنترل‌کننده then()  fetchData() قول را از processData(data) برمی‌گرداند. این همان چیزی است که به ما امکان می دهد آنها را به هم زنجیر کنیم.

مهم نیست اجرا شود: Promise.finally()

همانطور که try/catch به شما finally() می دهد، Promise.finally() بدون توجه به آنچه در زنجیره وعده اتفاق می افتد اجرا می شود:


fetchData()
  .then((data) => {
    console.log("Fetched data:", data);
    return processData(data); 
  })
  .then((processedData) => {
    console.log("Processed data:", processedData);
  })
  .catch((error) => {
    console.error("Error:", error);
  })
  .finally(() => {
    console.log("Cleaning up.");
  })

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

سریع شکست: Promise.all()

حالا اجازه دهید وضعیتی را در نظر بگیریم که در آن باید چندین تماس را به طور همزمان برقرار کنیم. فرض کنید باید دو درخواست شبکه ارائه کنیم و به نتایج هر دو نیاز داریم. اگر یکی از آنها شکست بخورد، می خواهیم کل عملیات را با شکست مواجه کنیم. رویکرد زنجیره‌ای ما در بالا می‌تواند کارساز باشد، اما ایده‌آل نیست زیرا مستلزم آن است که یک درخواست قبل از شروع درخواست بعدی تمام شود. در عوض، می‌توانیم از Promise.all():

استفاده کنیم


Promise.all([fetchData(), fetchOtherData()])
  .then((data) => { // data is an array
    console.log("Fetched all data:", data);
  })
  .catch((error) => {
    console.error("An error occurred with Promise.all:", error);
  });

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

اگر یکی از وعده‌های ارسال شده به Promise.all() با شکست مواجه شود، کل اجرا متوقف می‌شود و به catch() ارائه شده می‌رود. به این ترتیب، Promise.all() “سریع شکست می خورد.” 

همچنین می‌توانید از finally() با Promise.all() استفاده کنید، و همانطور که انتظار می‌رود رفتار خواهد کرد، مهم نیست که چگونه مجموعه وعده‌ها اجرا می‌شوند.< /p>

در روش then()، یک آرایه دریافت خواهید کرد که هر عنصر مربوط به وعده داده شده است، مانند:


Promise.all([fetchData(), fetchData2()])
  .then((data) => {
    console.log("FetchData() = " + data[0] + " fetchMoreData() = " + data[1] );
  })

اجازه دهید سریعترین برنده شود: Promise.race()

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

فرض کنید fetchData() و fetchSameData() دو روش برای درخواست اطلاعات یکسان هستند و هر دو وعده‌ها را برمی‌گردانند. در اینجا نحوه استفاده از race() برای مدیریت آنها آمده است:


Promise.race([fetchData(), fetchSameData()])
  .then((data) => {
    console.log("First data received:", data);
  });

در این حالت، پاسخ تماس then() فقط یک مقدار برای داده دریافت می کند—مقدار بازگشتی برنده (سریعترین) Promise.

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

آیا می توانید آن را انجام دهید؟

به عنوان یک چالش، چگونه می‌خواهید Promise.race() کار کند تا خطاها نادیده گرفته شوند و تنها اولین عملیات موفق برنده شود؟

همه یا هیچکدام: Promise.allSettled()

اگر می‌خواهید منتظر بمانید تا مجموعه‌ای از عملیات‌های همگام‌سازی کامل شوند، خواه شکست یا موفقیت‌آمیز شوند، می‌توانید از allSettled() استفاده کنید. به عنوان مثال:


Promise.allSettled([fetchData(), fetchMoreData()]).then((results) =>
  results.forEach((result) => console.log(result.status)),
);

آگومان نتایج که به کنترل کننده then() ارسال می شود، آرایه ای را نگه می دارد که نتایج عملیات را توصیف می کند، چیزی شبیه به:


[۰: {status: 'fulfilled', value: "This is the fetched data!"},
 ۱: {status: 'rejected', reason: undefined}]

بنابراین یک فیلد وضعیت دریافت می‌کنید که یا تکمیل شد یا رد شد. اگر برآورده شد (حل شد)، آنگاه مقدار آرگومان فراخوانی شده توسط resolve() را نگه می دارد. وعده‌های رد شده فیلد deason را با علت خطا پر می‌کنند، با فرض اینکه یکی از آنها ارائه شده باشد.

به زودی: Promise.withResolvers()

مشخصات ECMAScript 2024 شامل یک روش ثابت است. در Promise، به نام withResolvers(). اکثر مرورگرها و محیط های سمت سرور در حال حاضر از آن پشتیبانی می کنند. کمی باطنی است، اما موزیلا نمونه ای از نحوه استفاده. روش جدید به شما امکان می‌دهد یک Promise به همراه توابع resolve و reject را به‌عنوان متغیرهای مستقل اعلام کنید، در حالی که آنها را در یک محدوده نگه دارید.

نتیجه گیری

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