פיצ'ר חדש ב-ES2020 עוזר לנו לנהל את הפרומיסים שלנו טוב יותר בג'אווהסקריפט. ראשית, אני יוצא מנקודת הנחה שכולם יודעים מה זה פרומיסים. מדובר בפיצ'ר סופר משמעותי וחשוב שנכנס לפני כמה שנים לג'אווהסקריפט במסגרת הרפורמה של ES6 ועוזר לנו לנהל קוד באופן אסינכרוני. במאמר הזה הסברתי על פרומיסים ואיך משתמשים בהם (מומלץ גם להשלים את הקריאה עם async-await שנכנס ב-ES2017).
[אני מצטער בנוגע לשימוש בהיבריש אבל כשמדברים על Promises בכל מיני צווי פיתוח בדרך כלל אומרים פרומיסים ולא "הבטחות"]
נשאלת השאלה, איך בדיוק מנהלים כמה פרומיסים במקביל. כלומר נניח ויש לי כמה סרוויסים אסינכרוניים (נניח פונים אל שרת חיצוני כדי לקבל מידע). איך אני מנהל את כולם במקביל? יש לי כמה מתודות. הראשונה היא promise.all. מדובר במתודה שאליה אני יכול להכניס כמה פרומיסים. ברגע שכולם מתמלאים? אני מקבל מערך של כל התוצאות.
הנה דוגמת קוד ואחריה גם דוגמה ב-codepen שאפשר להשתעשע איתה:
const serviceMock1 = () => {
return new Promise((resolve, reject) => {
setTimeout(resolve, 100, 'Service mock 1 success!');
});
}
const serviceMock2 = () => {
return new Promise((resolve, reject) => {
setTimeout(resolve, 200, 'Service mock 2 success!');
});
}
const serviceMock3 = () => {
return new Promise((resolve, reject) => {
reject('Service mock FAIL!');
});
}
// Output ["Service mock 1 success!", "Service mock 2 success!"]
Promise.all([serviceMock1(), serviceMock2()]).then((values) => {
console.log(values); // ["Service mock 1 success!", "Service mock 2 success!"]
});
// Output 'Service mock FAIL!'
Promise.all([serviceMock1(), serviceMock2(), serviceMock3()]).then((values) => {
console.log('one rejected', values);
}, (error) => {
console.log('error', error) // 'Service mock FAIL!'
});
See the Pen promise.all demo by Ran Bar-Zik (@barzik-the-vuer) on CodePen.
מה יש לנו בדמו הזה? שלושה סרוויסים לדוגמה, אחד מהם מצליח להחזיר תשובה אחרי 100 מילישניות, השני מחזיר תשובה אחרי 200 מילישניות והשלישי נכשל מייד. כשאני קורא לשניים הראשונים עם Promise.all, אני מקבל הבטחה שמתממשת עם מערך נאה עם התשובות של הסרוויסים.
מה קורה כשאני קורה לשלושתם? זה שנכשל בעצם מונע מההבטחה של Promise.all להתממש והוא נכשל. בכשלון אני מקבל את הודעת הכשלון של הסרוויסים שנכשלו. אלו שהצליחו? אין לי מושג בנוגע אליהם.
ההתנהגות הזו נקראת short-circuit – בעברית – קצר. בדיוק כמו שהמפסק פחת בלוח החשמל קופץ כשיש בעיית חשמל אחת בכל הבית (למרות ששאר המכשירים בסדר) – כך גם Promise.all – הוא ייכשל למרות שכל הפרומיסים חוץ מאחד (!) הצליחו. מי הצליח? לא נדע.
דרך נוספת לניהול פרומיסים היא עם Promise.race.
המתודה הזו משגרת את כל ההבטחות. הראשונה שחוזרת מנצחת. בין אם מדובר בכשלון או בין אם מדובר בהצלחה. כלומר "מה שיוצא אני מרוצה". אבל רק ההבטחה הראשונה? השאר? מתפוגגות כמו הבטחה של פוליטיקאי אחרי יום בחירות.
הנה הדוגמה:
const serviceMock1 = () => {
return new Promise((resolve, reject) => {
setTimeout(resolve, 100, 'Service mock 1 success!');
});
}
const serviceMock2 = () => {
return new Promise((resolve, reject) => {
setTimeout(resolve, 200, 'Service mock 2 success!');
});
}
const serviceMock3 = () => {
return new Promise((resolve, reject) => {
reject('Service mock FAIL!');
});
}
// Output "Service mock 1 success!"
Promise.race([serviceMock1(), serviceMock2()]).then((values) => {
console.log(values); // "Service mock 1 success!"
});
// Output 'Service mock FAIL!'
Promise.race([serviceMock1(), serviceMock2(), serviceMock3()]).then((values) => {
console.log('one rejected', values);
}, (error) => {
console.log('error', error) // 'Service mock FAIL!'
});
See the Pen promise.race demo by Ran Bar-Zik (@barzik-the-vuer) on CodePen.
אפשר לראות שבדוגמה הראשונה אני משגר שני פרומיסים עם Promise.race. אני מקבל הבטחה שמתממשת עם התוצאה הראשונה – הפרומיס שמצליח אחרי 100 מילישניות. מה קורה עם השני? כבר לא מעניין את Promise.race. כשמו כן הוא – מי שמצליח מצליח ראשון וכל השאר? לאבדון.
בדוגמה השניה אני משגר את שלושת הפרומיסים. הכשלון של השלישי הוא זה ש"מנצח" ו-Promise.race מחזירה אותו. כל השאר שהצליחו? מעניינים את הסבתא.
ואחרי ההקדמה המאוד ארוכה הזו, הגיעה העת להסביר על Promise.allSettled. היא מחזירה פשוט מערך עם תוצאות כל הפרומיסים – מי שנכשל ומי שהצליח. הכי פשוט בעולם. פשוט עד כדי גיחוך. בכל איבר בתוצאה יש אובייקט עם סטטוס (הצליח או נכשל) והתוצאה. פשוט, קל ואלגנטי. הנה הדוגמה:
const serviceMock1 = () => {
return new Promise((resolve, reject) => {
setTimeout(resolve, 100, 'Service mock 1 success!');
});
}
const serviceMock2 = () => {
return new Promise((resolve, reject) => {
setTimeout(resolve, 200, 'Service mock 2 success!');
});
}
const serviceMock3 = () => {
return new Promise((resolve, reject) => {
reject('Service mock FAIL!');
});
}
/*
Output:
[{status: "fulfilled", value: "Service mock 1 success!"},
{status: "fulfilled", value: "Service mock 2 success!"}]
*/
Promise.allSettled([serviceMock1(), serviceMock2()]).then((values) => {
console.log(values); // "Service mock 1 success!"
});
/*
Output:
[{status: "fulfilled", value: "Service mock 1 success!"},
{status: "fulfilled", value: "Service mock 2 success!"},
{status: "rejected", reason: "Service mock FAIL!"}]
*/
Promise.allSettled([serviceMock1(), serviceMock2(), serviceMock3()]).then((values) => {
console.log(values);
}, (error) => {
console.log('error', error);
});
See the Pen promise.allSettled demo by Ran Bar-Zik (@barzik-the-vuer) on CodePen.
הדוגמה היא פשוטה למדי, promise.allSettled לעולם לא מקצר. הוא פשוט מחזיר את הכל. ההצלחה, הכשלון, הביזיון והנחמה. כפי שנאמר: "תאמר שפחתך יהיה־נא דבר־אדני המלך למנחה כי׀ כמלאך האלהים כן אדני המלך לשמע הטוב והרע וה' אלהיך יהי עמך". אנחנו מקבלים גם את הטוב, גם את הרע. ותודו שזה בדיוק מה שרציתם לראות בבלוג טכנולוגי: פסוקים מהתנ"ך (ספר שמואל ב' לכל הבורים).
בניגוד, אולי, לתנ"ך, promise.allSettled נתמך כבר בכל הדפדפנים חוץ מאקספלורר אהובנו אבל בשביל כל הענתיקות יש את babel להזכירכם.
4 תגובות
איך עושים race של רק המוצלחים.
כלומר אם שילחתי 3 פרומיסים, ואחד חזר מהר אבל עם כישלון אמשיך לחכות עד לראשון מבין ה-2 שנותרו שיחזור עם עלה של זית (הצלחה)?
(אם ורק אם כולם יכשלו יוחזר כישלון)
יש דרך לממש את זה?
בשיטה הישנה והטובה
פשוט לקשר ב-then של הפרומיס את אותה הפונקצייה ואז כל פרומיס שיסתיים יקרא לפונקצייה בלי לחכות לאחרות.
בדוגמא של רן:
נראה לי שברובהמקרים זה יהיה הדרך הכי טובה להשתמש ב-promise
אם allSettled מחזירה פרומסז שהצליחו ושנכשלו, מתי allSettled יכולה להיכשל? במילים אחרות, מתי ה-error callback של allSettled ייקרא?