• בלוג
  • בואו נממש לבד את Promise.any בשביל להבין איך זה עובד

בואו נממש לבד את Promise.any בשביל להבין איך זה עובד

14/03/2021

אני ממשיך בסידרת ES2021 של הבלוג והפיצ'ר של היום הוא התוספת Promise.any - פונקציה לא כל כך מלהיבה עם Use Case אחד שימושי. בואו נראה מה היא עושה ונבנה גירסה פשוטה משלנו כדי להבין איך זה עובד.

1. הפונקציה Promise.any

הפונקציה Promise.any מקבלת אוסף של Promises ומחזירה את התוצאה של ה Promise הראשון מהאוסף שהצליח, או אם כולם נכשלו היא תחזיר מערך עם כל הכישלונות. הנה דוגמה פשוטה לשימוש בפונקציה:

function sleep(ms) {
  return new Promise((resolve, reject) => {
    if (ms <= 0) {
      reject("Invalid Sleep Time");
    } else {
      setTimeout(() => (resolve(ms)), ms);
    }
  });
}


Promise.any([sleep(10), sleep(20), sleep(-5)]).then((ms) => console.log(`Done: ${ms}`));

הקוד כבר עובד בדפדפנים הקרובים אליכם ומדפיס על המסך את ההודעה: Done: 10.

2. איך בונים את זה

מנגנון Promises ב JavaScript קנה לעצמו שם של מנגנון די מסובך אבל לדעתי לא בצדק. הבטחה היא בסך הכל אוביקט שיכול להתממש או להיכשל מתישהו בעתיד, ואנחנו יכולים להוסיף לאותה הבטחה הוראות שיגידו לה מה לעשות אם הפעולה הצליחה או נכשלה. ה Promise היא בסך הכל דרך לייצג חישוב אסינכרוני.

במקרה של Promise.any אנחנו רוצים להוסיף לכל אחת מההבטחות שקיבלנו שני קטעי קוד:

  1. אם ההבטחה מצליחה אני יכול להחזיר את הערך שלה לקוד שיצר אותי.

  2. אם ההבטחה נכשלה אני מוסיף את הכישלון לרשימת הכשלונות ובודק אם יש עדיין חישובים פעילים ברקע. אם אין אני מסיים ומחזיר את רשימת הכישלונות בתור כישלון שלי.

קוד? ברור:

Promise.any = function(promises) {
  return new Promise((resolve, reject) => {
    const errors = [];
    let activePromisesCount = promises.length;

    function successHandler(...value) {
      activePromisesCount--;
      resolve(...value);
    }

    function errorHandler(err) {
      activePromisesCount--;
      errors.push(err);

      if (activePromisesCount === 0) {
        reject(errors);
      }
    }

    for (const p of promises) {
      p.then(successHandler).catch(errorHandler);
    }
  });
}

הגירסה שלי יותר פשוטה מה Spec ולא מטפלת בכל המקרים (לדוגמה פונקציית Promise.any האמיתית תיכשל אם מערך ה Promises שקיבלה הוא ריק), אבל היא כן עוזרת לנו להבין איך לשחק עם אוביקטי Promise:

  1. כשיש לי אוביקט Promise ביד אני יכול להוסיף לו קוד הצלחה עם then וקוד טיפול בשגיאות עם catch.

  2. אם אני מוסיף קודים כאלה למספר Promises, אני יכול לסנכרן ביניהן באמצעות משתנים משותפים.

המשתנה errors מייצג את כל השגיאות בחישובים וכך מאפשר לי בסוף להחזיר את השגיאות של כולם במכה אחת; המשתנה activePromisesCount מייצג את מספר החישובים הפעילים, ובאמצעות הורדה שלו ב-1 כל פעם שחישוב מסתיים אני יכול לדעת מתי הסתיימו כל החישובים.

למידע נוסף על הפונקציה מומלץ לקרוא בדף התיעוד בקישור https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/any.

3. למה זה טוב

עכשיו אנחנו מגיעים ל Money Time של Promise.any ואני חייב להודות שהתשובה לא מאוד פשוטה. ה Use Case היחיד שהצלחתי לגרד לפונקציה זו יהיה בבניית סוג של Load Balancer הפוך בצד הלקוח, כלומר קוד שצריך למשוך מידע משרת ויודע שהמידע הזה זמין על מספר שרתים אז הוא יפנה לכולם וימשיך ברגע שאחד עונה לו. במילים אחרות משהו בסגנון הזה:

const userData = await Promise.any([
      axios.get('https://server1.app.com/user/10'),
      axios.get('https://server2.app.com/user/10'),
      axios.get('https://server3.app.com/user/10'),
    ]);

console.log(userData);

הקוד פונה ל-3 שרתים במקביל כדי למשוך את נתוני המשתמש וידפיס את המידע מהשרת הראשון שיענה. החיסרון הוא עומס על השרתים כי עכשיו כל בקשת Ajax פונה ל-3 שרתים, אבל במצבים מסוימים מנגנון כזה אולי יכול לשפר ביצועים בצד לקוח בצורה שתהיה שווה את זה.

אם יש לכם רעיונות נוספים מה אפשר לעשות עם Promise.any אשמח לשמוע בתגובות.