Anophel-آنوفل بررسی عمیق Promise ها در جاوا اسکریپت

بررسی عمیق Promise ها در جاوا اسکریپت

انتشار:
1

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


Promise های جاوا اسکریپت چیست؟

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

Promise یک آبجکت جاوا اسکریپت ویژه است که نتیجه نهایی چنین عملیات ناهمزمانی را نشان می دهد. مانند یک پروکسی برای نتیجه عملیات عمل می کند.

روزهای بد قدیمی: Callback Functions

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

setTimeout(function() {
  console.log('Hello, World!');
}, 1000);

در اینجا، setTimeout یک تابع ناهمزمان(asynchronous) است که ه رcallback را که پس از تعداد مشخصی از میلی‌ثانیه ارسال شود، اجرا می‌کند. در این مورد، "!Hello, World" را ثبت می کند. پس از گذشت یک ثانیه به کنسول.

حالا تصور کنید می‌خواهیم هر ثانیه یک پیام را به مدت پنج ثانیه ثبت کنیم. که شبیه این خواهد بود:

setTimeout(function() {
  console.log(1);
  setTimeout(function() {
    console.log(2);
    setTimeout(function() {
      console.log(3);
      setTimeout(function() {
        console.log(4);
        setTimeout(function() {
          console.log(5);
        }, 1000);
      }, 1000);
    }, 1000);
  }, 1000);
}, 1000);

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

مسلماً، این یک مثال ساختگی است، اما برای نشان دادن موضوع مفید است. در یک سناریوی واقعی، ممکن است یک تماس Ajax برقرار کنیم، DOM را با نتیجه به روز کنیم، سپس منتظر بمانیم تا انیمیشن کامل شود. یا، سرور ما ممکن است ورودی را از کلاینت دریافت کند، آن ورودی را تأیید کند، پایگاه داده را به روز کند، در یک فایل گزارش بنویسد و در نهایت یک پاسخ ارسال کند. در هر دو مورد، ما همچنین باید هر گونه خطای رخ داده را مدیریت کنیم.

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

نحوه ایجاد یک آبجکت Promise جاوا اسکریپت

سینتکس اصلی برای ایجاد یک Promise به شرح زیر است:

const promise = new Promise((resolve, reject) => {
  //asynchronous code goes here
});

ما با نمونه سازی یک آبجکت Promise جدید با استفاده از سازنده Promise و ارسال تابع callback به آن شروع می کنیم. callback دو آرگومان resolve و Reject می گیرد که هر دو تابع هستند. تمام کدهای ناهمزمان ما داخل آن callback قرار می گیرد.

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

برای اینکه ببینید در عمل چگونه کار می کند، کد زیر را در نظر بگیرید. این یک درخواست ناهمزمان به یک وب سرویس می دهد که یک شوخی تصادفی dad را در قالب JSON برمی گرداند:

const promise = new Promise((resolve, reject) => {
  const request = new XMLHttpRequest();
  request.open('GET', 'https://icanhazdadjoke.com/');
  request.setRequestHeader('Accept', 'application/json');

  request.onload = () => {
    if (request.status === 200) {
      resolve(request.response); // we got data here, so resolve the Promise
    } else {
      reject(Error(request.statusText)); // status is not 200 OK, so reject
    }
  };

  request.onerror = () => {
    reject(Error('Error fetching data.')); // error occurred, reject the  Promise
  };

  request.send(); // send the request
});

سازنده Promise

ما با استفاده از سازنده Promise برای ایجاد یک آبجکت Promise جدید شروع می کنیم. سازنده برای بسته بندی توابع یا API هایی استفاده می شود که قبلاً از Promise ها پشتیبانی نمی کنند، مانند آبجکت XMLHttpRequest در بالا. callback ارسال شده به سازنده Promise حاوی کد ناهمزمان مورد استفاده برای دریافت داده از سرویس راه دور است. (توجه داشته باشید که ما در اینجا از یک تابع arrow استفاده می کنیم.) در داخل callback، یک درخواست Ajax به https://icanhazdadjoke.com/ ایجاد می کنیم که یک جوک تصادفی dad را با فرمت JSON برمی گرداند.

هنگامی که یک پاسخ موفقیت آمیز از سرور راه دور دریافت می شود، به متد resolve ارسال می شود. در صورت بروز هرگونه خطایی - چه در سرور و چه در سطح شبکه - Reject با یک آبجکت Error فراخوانی می شود.

متد then 

هنگامی که یک آبجکت Promise را نمونه سازی می کنیم، یک پروکسی برای داده هایی دریافت می کنیم که در آینده در دسترس خواهد بود. در مورد ما، ما انتظار داریم برخی از داده ها از سرویس راه دور بازگردانده شوند. بنابراین، چگونه متوجه شویم که داده ها چه زمانی در دسترس هستند؟ در اینجا از تابع ()Promise.then استفاده می شود:

const promise = new Promise((resolve, reject) => { ... });

promise.then((data) => {
  console.log('Got data! Promise fulfilled.');
  document.body.textContent = JSON.parse(data).joke;
}, (error) => {
  console.error('Promise rejected.');
  console.error(error.message);
});

این تابع می تواند دو آرگومان داشته باشد: یک callback موفقیت و یک callback شکست. این تماس‌ها زمانی فراخوانی می‌شوند که Promise تسویه شود (یعنی یا محقق شود یا رد شود). اگر Promise محقق شد، پاسخ callback موفقیت آمیز با داده های واقعی که برای resolve آن ارسال کردیم، فعال می شود. در صورت رد شدن Promise، callback Reject فراخوانی می شود. هر آنچه را که برای Reject  تصویب کردیم، به عنوان یک آرگومان برای این تماس ارسال می شود.

State های یک Promise جاوا اسکریپت چیست؟

در کد بالا دیدیم که می توانیم با فراخوانی متدهای resolve یا Reject ، وضعیت(state) یک Promise را تغییر دهیم. قبل از ادامه دادن، اجازه دهید یک ثانیه به چرخه عمر(lifecycle) یک Promise نگاه کنیم.

یک Promise ممکن است در یکی از این حالات باشد:

  1. انتظار pending
  2. برآورده شد fulfilled
  3. رد شد rejected
  4. مستقر شده settled


یک Promise سیکل اول زندگی را در حالت pending شروع می کند. یعنی نه fulfilled شده و نه رد شده است. اگر عمل مربوط به Promise موفقیت آمیز باشد (یک تماس API راه دور در مورد ما) و متد resolve فراخوانی شود، گفته می شود که Promise محقق(براآورد) شده است. از طرف دیگر اگر عمل مربوطه ناموفق باشد و متد Reject خوانده شود، Promise در حالت rejected است. در نهایت، گفته می‌شود که یک Promise در صورت تحقق یا رد شدن، اما pending نیست، settled می‌شود.

هنگامی که یک Promise رد یا محقق شود، این state برای همیشه با آن مرتبط می شود. این بدان معناست که یک Promise می تواند فقط یک بار موفق شود یا شکست بخورد. اگر Promise قبلاً محقق شده باشد و بعداً با دو callback به آن یک ()then متصل کنیم، callback موفقیت به درستی فراخوانی می شود. بنابراین، در دنیای Promise ها، ما علاقه ای نداریم بدانیم که چه زمانی Promise ما resolve می شود. ما فقط نگران نتیجه نهایی Promise هستیم.

اما آیا نباید از Fetch API استفاده کنیم؟

در این مرحله، ممکن است بپرسیم که چرا از Fetch API برای Fetch داده‌ها از سرور راه دور استفاده نمی‌کنیم، و پاسخ این است که احتمالاً باید این کار را انجام دهیم.

برخلاف آبجکت XMLHttpRequest،در Fetch API مبتنی بر Promise است، به این معنی که می‌توانیم کد خود را مانند این بازنویسی کنیم (منهای مدیریت خطا):

fetch('https://icanhazdadjoke.com', { 
  headers: { 'Accept': 'application/json' }
})
  .then(res => res.json())
  .then(json => console.log(json.joke));

دلیل استفاده از XMLHttpRequest این بود که بینش بیشتری در مورد آنچه در عمق می گذرد ارائه دهیم.

Promise های زنجیره ای

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

همانطور که قبلاً انجام دادیم می توانیم با ایجاد یک آبجکت Promise جدید شروع کنیم:

const promise = new Promise((resolve, reject) => {
  setTimeout(() => { resolve() }, 1000)
});

promise.then(() => {
  console.log(1);
});

همانطور که انتظار می رفت، پس از گذشت یک ثانیه و ورود "1" به کنسول، این Promise حل می شود.

برای ادامه زنجیره، باید Promise دوم را بعد از دستور کنسول خود برگردانیم و then را به یک ثانیه ارسال کنیم و سپس:

const promise = new Promise((resolve, reject) => {
  setTimeout(() => { resolve() }, 1000)
});

promise.then(() => {
  console.log(1);
  return new Promise((resolve, reject) => {
    setTimeout(() => { resolve() }, 1000)
  });
}).then(() => {
  console.log(2);
});

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

function sleep(ms) {
  return new Promise(resolve => setTimeout(resolve, ms));
}

سپس می توانیم از آن برای بهتر کردن کد تودرتو استفاده کنیم:

sleep(1000)
  .then(() => {
    console.log(1);
    return sleep(1000);
  }).then(() => {
    console.log(2);
    return sleep(1000);
  }).then(() => {
    console.log(3);
    return sleep(1000);
  })

و از آنجایی که متد then یک آبجکت Promise را برمی‌گرداند و ما هیچ مقداری را از یک عملیات ناهمزمان به عملیات دیگر منتقل نمی‌کنیم، این به ما امکان می‌دهد تا کارها را ساده‌تر کنیم:

sleep(1000)
  .then(() => console.log(1))
  .then(() => sleep(1000))
  .then(() => console.log(2))
  .then(() => sleep(1000))
  .then(() => console.log(3))
  ...

این بسیار زیباتر از کد اصلی است.

انتقال داده ها در زنجیره های Promise

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

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

fetch('https://api.github.com/repos/eslint/eslint/contributors')
  .then(res => res.json())
  .then(json => {
    const firstContributor = json[0].login;
    return fetch(`https://api.github.com/users/${firstContributor}`)
  })
  .then(res => res.json())
  .then(json => console.log(`The first contributor to ESLint was ${json.name}`));

// The first contributor to ESLint was Nicholas C. Zakas

همانطور که می بینیم، با برگرداندن Promise بازگشتی از تماس دوم fetch ، پاسخ سرور (res) در زیر در دسترس است و then مسدود می شود.

Promise Error Handling

قبلاً دیدیم که تابع then دو تابع callback را به عنوان آرگومان می گیرد و در صورت رد شدن Promise، تابع دوم فراخوانی می شود:

promise.then((data) => {
  console.log('Got data! Promise fulfilled.');
  ...
}, (error) => {
  console.error('Promise rejected.');
  console.error(error.message);
});

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

متد catch

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

بیایید کد قبلی را به عنوان مثال در نظر بگیریم:

fetch('https://api.github.com/repos/eslint/eslint/contributors')
  .then(res => res.json())
  .then(json => {
    const firstContributor = json[0].login;
    return fetch(`https://api.github.com/users/${firstContributor}`)
  })
  .then(res => res.jsn())
  .then(json => console.log(`The top contributor to ESLint wass ${json.name}`))
  .catch(error => console.log(error));

توجه داشته باشید که علاوه بر اضافه کردن یک کنترل کننده خطا در انتهای بلوک کد، من ()res.json را به صورت res.jsn در خط هفتم اشتباه نوشتم.

حالا وقتی کد را اجرا می کنیم، خروجی زیر را روی صفحه می بینیم:

TypeError: res.jsn is not a function
  <anonymous>  http://0.0.0.0:8000/index.js:7  
  promise callback*  http://0.0.0.0:8000/index.js:7  

index.js:9:27

فایلی که من در آن کار می کنم index.js نام دارد. خط 7 حاوی error است و خط 9 بلوک catch است که خطا را دریافت کرده است.

متد finally

متد Promise.finally زمانی اجرا می شود که Promise تسویه شود - یعنی یا resolve شود یا rejected شود. مانند catch، برای جلوگیری از تکرار کد کمک می کند و برای انجام کارهای پاکسازی، مانند بستن اتصال پایگاه داده، یا حذف اسپینر بارگیری از رابط کاربری بسیار مفید است.

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

function getFirstContributor(org, repo) {
  showLoadingSpinner();
  fetch(`https://api.github.com/repos/${org}/${repo}/contributors`)
  .then(res => res.json())
  .then(json => {
    const firstContributor = json[0].login;
    return fetch(`https://api.github.com/users/${firstContributor}`)
  })
  .then(res => res.json())
  .then(json => console.log(`The first contributor to ${repo} was ${json.name}`))
  .catch(error => console.log(error))
  .finally(() => hideLoadingSpinner());
};

getFirstContributor('facebook', 'react');

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

متد های Promise بیشتر

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

()Promise.all

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

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

Promise.all([
  new Promise((resolve, reject) => setTimeout(() => resolve(1), 0)),
  new Promise((resolve, reject) => setTimeout(() => resolve(2), 1500)),
  new Promise((resolve, reject) => setTimeout(() => resolve(3), 3000)),
])
  .then(values => console.log(values))
  .catch(err => console.error(err));

کد بالا پس از سه ثانیه [1، 2، 3] به کنسول وارد می شود.

با این حال، اگر هر یک از Promise ها رد شود، همه با مقدار آن Promise رد می شوند و هیچ یک از Promise دیگر را در نظر نمی گیرند.

()Promise.allSettled

برخلاف all این متد Promise.allSettled منتظر هر یک از Promiseی می‌ماند که برای اجرا یا رد کردن آن‌ها تصویب شده است. در صورت رد شدن Promise، اجرا را متوقف نمی کند:

Promise.allSettled([
  new Promise((resolve, reject) => setTimeout(() => resolve(1), 0)),
  new Promise((resolve, reject) => setTimeout(() => reject(2), 1500)),
  new Promise((resolve, reject) => setTimeout(() => resolve(3), 3000)),
])
  .then(values => console.log(values))
  .catch(err => console.error(err));

این لیستی از وضعیت ها و مقادیر (در صورت تحقق Promise) یا دلایل (در صورت رد شدن) را برمی گرداند:

[
  { status: "fulfilled", value: 1 },
  { status: "rejected", reason: 2 },
  { status: "fulfilled", value: 3 },
]

()Promise.any

Promise.any مقدار اولین Promise را که باید محقق شود برمی گرداند. اگر هر Promise ای رد شود، این موارد نادیده گرفته می شوند:

Promise.any([
  new Promise((resolve, reject) => setTimeout(() => reject(1), 0)),
  new Promise((resolve, reject) => setTimeout(() => resolve(2), 1500)),
  new Promise((resolve, reject) => setTimeout(() => resolve(3), 3000)),
])
  .then(values => console.log(values))
  .catch(err => console.error(err));

این "2" را پس از یک و نیم ثانیه به کنسول وارد می کند.

()Promise.race

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

Promise.race([
  new Promise((resolve, reject) => setTimeout(() => reject('Rejected with 1'), 0)),
  new Promise((resolve, reject) => setTimeout(() => resolve(2), 1500)),
  new Promise((resolve, reject) => setTimeout(() => resolve(3), 3000)),
])
  .then(values => console.log(values))
  .catch(err => console.error(err));

این کار "Rejected with 1" را به کنسول وارد می کند، زیرا اولین Promise در آرایه بلافاصله رد می شود و رد توسط بلوک catch ما گرفته می شود.

ما می توانیم چیزهایی مانند این را تغییر دهیم:

Promise.race([
  new Promise((resolve, reject) => setTimeout(() => resolve('Resolved with 1'), 0)),
  new Promise((resolve, reject) => setTimeout(() => resolve(2), 1500)),
  new Promise((resolve, reject) => setTimeout(() => resolve(3), 3000)),
])
  .then(values => console.log(values))
  .catch(err => console.error(err));

با این کار "Resolved with 1" به کنسول وارد می شود.

در هر دو مورد، دو Promise دیگر نادیده گرفته می شود.

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

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

مشارکت‌کننده اصلی مخزن GitHub را پیدا کنید

این اولین نسخه نمایشی به کاربر اجازه می دهد تا URL یک مخزن GitHub را وارد کند. سپس یک درخواست Ajax برای بازیابی لیستی از 30 مشارکت کننده اول در آن مخزن ارائه می کند. هنگامی که این درخواست تکمیل شد، درخواست دوم را برای بازیابی نام مشارکت کننده اصلی و نمایش آن در صفحه انجام می دهد. برای انجام این کار، فراخوانی دوم fetch از داده های بازگردانده شده توسط اولی استفاده می کند.

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

function sleep(ms) {
  return new Promise(resolve => setTimeout(resolve, ms));
}

function showLoadingSpinner() {
  document.querySelector('.overlay').style.display = 'grid';
}

function hideLoadingSpinner() {
  document.querySelector('.overlay').style.display = 'none';
}

function getFirstContributor(org, repo) {
  showLoadingSpinner();
  sleep(500).
  then(() => fetch(`https://api.github.com/repos/${org}/${repo}/contributors`))
  .then(res => res.json())
  .then(json => {
    const firstContributor = json[0].login;
    return fetch(`https://api.github.com/users/${firstContributor}`);
  })
  .then(res => res.json())
  .then(json => {
    document.getElementById('result').innerText = `The original contributor to ${repo} was ${json.name}`;
  })
  .catch(error => console.error(error))
  .finally(() => hideLoadingSpinner());
}

const form = document.querySelector('form');

form.addEventListener('submit', (e) => {
  e.preventDefault();
  const url = document.getElementById('repoURL').value;
  const [org, repo] = url.split('/').filter(Boolean).slice(-2) 
  getFirstContributor(org, repo);
});
<div class="overlay">
  <div class="spinner-border text-light" role="status">
    <span class="visually-hidden">Loading...</span>
  </div>
</div>

<form>
  <div class="mb-3">
    <p id="result"></p>
    <label for="repoURL" class="form-label">Enter a repo URL</label>
    <input type="url" required class="form-control" id="repoURL" placeholder="https://github.com/facebook/react">
  </div>
  
  <button type="submit" class="btn btn-primary">Submit</button>
</form>

تعیین کنید کدام مخزن GitHub ستاره های بیشتری دارد

در این مثال کاربر می تواند آدرس دو مخزن GitHub را وارد کند. سپس اسکریپت از Promise.all برای ایجاد دو درخواست به صورت موازی برای fetch برخی اطلاعات اولیه در مورد این مخازن استفاده می کند. ما می توانیم از all استفاده کنیم، زیرا دو درخواست شبکه کاملاً مستقل از یکدیگر هستند. برخلاف مثال قبلی، نتیجه یکی بر نتیجه دیگری نیست.

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

function sleep(ms) {
  return new Promise(resolve => setTimeout(resolve, ms));
}

function showLoadingSpinner() {
  document.querySelector('.overlay').style.display = 'grid';
}

function hideLoadingSpinner() {
  document.querySelector('.overlay').style.display = 'none';
}

function getStars(firstRepoURL, secondRepoURL) {
  showLoadingSpinner();
  const [org1, repo1] = firstRepoURL.split('/').filter(Boolean).slice(-2);
  const [org2, repo2] = secondRepoURL.split('/').filter(Boolean).slice(-2);

  sleep(500).
    then(() => Promise.all([
        fetch(`https://api.github.com/repos/${org1}/${repo1}`),
        fetch(`https://api.github.com/repos/${org2}/${repo2}`),
      ])
    )
    .then(responses => Promise.all(responses.map(res => res.json())))
    .then(responses => {
      const starsRepo1 = responses[0].stargazers_count
      const starsRepo2 = responses[1].stargazers_count
      const [most, least] = starsRepo1 > starsRepo2 ? [repo1, repo2] : [repo2, repo1]
      document.getElementById('result').innerText = `${most} has more stars than ${least}`;
    })
    .catch(err => console.error(err))
    .finally(() => hideLoadingSpinner());
}

const form = document.querySelector('form');

form.addEventListener('submit', (e) => {
  e.preventDefault();
  const firstRepoURL = document.getElementById('firstRepoURL').value;

  const secondRepoURL = document.getElementById('secondRepoURL').value;

  getStars(firstRepoURL, secondRepoURL);
});
<div class="overlay">
  <div class="spinner-border text-light" role="status">
    <span class="visually-hidden">Loading...</span>
  </div>
</div>

<form>
  <div class="mb-3">
    <p id="result"></p>
    <label for="firstRepoURL" class="form-label">First repo URL</label>
    <input type="url" required class="form-control" id="firstRepoURL" placeholder="https://github.com/facebook/react">
    <label for="secondRepoURL" class="form-label">Second repo URL</label>
    <input type="url" required class="form-control" id="secondRepoURL" placeholder="https://github.com/vuejs/core">
  </div>
  
  <button type="submit" class="btn btn-primary">Submit</button>
</form>

Promise‌ها، callback ها یا async… await: کدام یک را باید استفاده کنیم؟

تا کنون، ما بهcallback ها و Promise‌ها نگاه کرده‌ایم، اما لازم به ذکر است که سینتکس جدیدتر async ... await . در حالی که به طور مؤثر فقط قند نحوی در بالای Promise‌ها قرار دارد، در بسیاری از شرایط می‌تواند خواندن و درک کد مبتنی بر Promise را آسان‌تر کند.

به عنوان مثال، به این صورت است که می توانیم کد قبلی خود را بازنویسی کنیم:

async function getFirstContributor(org, repo) {
  showLoadingSpinner();
  try {
    const res1 = await  fetch(`https://apiy.github.com/repos/${org}/${repo}/contributors`);
    const contributors = await res1.json();
    const firstContributor = contributors[0].login;
    const res2 = await fetch(`https://api.github.com/users/${firstContributor}`)
    const details = await res2.json();
    console.log(`The first contributor to ${repo} was ${details.name}`);
  } catch (error) {
    console.error(error)
  } finally {
    hideLoadingSpinner();
  }
}

getFirstContributor('facebook', 'react');

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

به نظر من تجزیه کد بالا کمی ساده تر از نسخه مبتنی بر Promise است. با این حال، من شما را تشویق می کنم که با syntax async ... await آشنا شوید و ببینید چه چیزی برای شما بهتر است.

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

نتیجه

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

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

اگر سوال یا نظری دارید، در قسمت نظرات به من اطلاع دهید.

#Promise#جاوااسکریپت#callback
نظرات ارزشمند شما :

در حال دریافت...

مقاله های مشابه

در حال دریافت...