הבטחות שבורות?
משמיטים שגיאות, מאבדים תוצאות...
האם הבטחות JavaScript שבורות?
בימי קדם
אחד המיתוסים הנפוצים ביותר על הבטחות הוא כביכול חסרונות הטיפול בשגיאות.
לפני שנים רבות הבטחות היו באמת נוראיות עם שגיאות. הרבה עבודה הושקעה בתיקון זה.
והנה, זה תוקן, ואף נפרס באופן נרחב.
אנשים שמחו
ולצערנו, חלק לא שמו לב.
הזמנים הנוכחיים
המיתוס עדיין נמשך, אני רואה אותו בכל מקום: מאמרים פופולריים ב-medium, ב-dzone, ורבים מקורות אחרים.
אני מודה, אפילו משאבים ותיעוד “רשמיים” מציעים בעיקר דוגמאות חלשות והרגלים רעים. אלה משמשים לעתים קרובות כדי “להוכיח” את הטענה נגד הבטחות. חלק אף מציעים “תרופות” שמחמירות את המצב הרבה יותר. (הערה: הקישור הוסר)
כללים להימנעות מצרות
- הבטחות צריכות משהו להיאחז בו
- תמיד החזר (
return) מהפונקציות שלך.
- תמיד החזר (
- השתמש במופעי
Errorאמיתיים- תמיד השתמש במופעי
Error.
- תמיד השתמש במופעי
- טפל בשגיאות היכן שזה הגיוני
- תמיד השתמש ב-
.catch(), לפחות פעם אחת.
- תמיד השתמש ב-
- הוסף בהירות עם פונקציות בעלות שם 🦄✨
- עדיף להשתמש בפונקציות בעלות שם.
#1 הבטחות צריכות משהו להיאחז בו
חשוב מאוד תמיד להחזיר (return) מהפונקציות שלך.
פונקציות הקולבק של הבטחות פועלות לפי תבנית מסוימת ב-.then(callback) וב-.catch(callback).
כל ערך שמוחזר מועבר לקולבק של ה-.then() הבא.
function addTen(number) { return number + 10;}
Promise.resolve(10) // 10 .then(addTen) // 20 .then(addTen) // 30 .then(addTen) // 40 .then(console.log) // logs "40"בונוס של “תמיד להחזיר”: הקוד הרבה יותר קל לבדיקות יחידה.
שאלה: כמה מצבי Promise נפרדים (resolved ו-rejected) נוצרו?
שאלה: כמה promises נוצרו בדוגמה הקודמת?
#2 השתמש במופעי Error אמיתיים
ל-JavaScript יש התנהגות מעניינת סביב שגיאות (החלה על קוד אסינכרוני וסינכרוני).
[ראה דוגמה ב-repl.it:throwing errors in javascript]
כדי לקבל פרטים שימושיים על מספר השורה ו-call stack, עליך להשתמש במופעי Error. זריקת מחרוזות לא עובדת כמו ב-Python או Ruby.
למרות ש-JavaScript נראה כמטפל ב-throw "string", תראה את המחרוזת ב-handler של catch. עם זאת, הנתונים הם כל מה שתראה*. שום stack frames קודמים לא ייכללו.
דוגמאות נכונות של new Error:
throw new Error('message') // ✅Promise.reject(new Error('message')) // ✅throw Error('message') // ✅Promise.reject(Error('message')) // ✅הבאים הם anti-patterns נפוצים:
throw 'error message' // ❌Promise.reject(-42) // ❌#3 טיפול בשגיאות במקום שבו זה הגיוני
Promises מספקות דרך חלקה לטיפול בשגיאות באמצעות .catch(). זה בעצם סוג מיוחד של .then() – שבו כל שגיאה מ-.then() קודמים מטופלת. בואו נסתכל על דוגמה…
Promise.resolve(42) .then(() => 'hello') .catch(() => console.log('will not get hit')) .then(() => throw new Error('totes fail')) .catch(() => console.log('WILL get hit'))בעוד ש-.catch() עשוי להיראות כמו מאזין אירועי DOM (למשל click, keypress), המיקום שלו חשוב – הוא יכול “לתפוס” רק שגיאות שנזרקות מעליו.
דחיסת שגיאות היא יחסית טריוויאלית – החזר ערך שאינו שגיאה ב-callback של .catch(), ושרשרת ה-Promise תעבור להרצת ה-callbacks של .then() ברצף. (למעשה.)
נסו לעקוב אחר הרצף בדוגמה הבאה:
Promise.resolve(42) .then(() => 'hello') .then(() => throw new Error('totes fail')) .catch(() => { return 99 }) .then(num => num + 1) .then(console.log) // expected output: 100הרצף הוא מה שחשוב להבין.
למרות שמדובר בדוגמה מטופשת, היא נועדה להמחיש כיצד שגיאות ונתונים זורמים ב-Promises.
הנה תיאור הרצף:
- 42 הוא הערך ההתחלתי.
helloתמיד מוחזר על ידי המתודה הבאה.- אנו מתעלמים מהערך הקודם וזורקים שגיאה עם ההודעה
'totes fail'. .catch()מיירט את השגיאה, ובמקום זאת מחזיר99שיטופל על ידי כל.then()עוקב.- מגדילים את
num, ומחזירים100. - המתודה
console.logמקבלת100ומדפיסה אותו! :tada:
שאלה: מה קורה כאשר יש שני .catch() ברצף? האם השני יכול אי פעם לרוץ? האם אתם יכולים לחשוב על מקרה שימוש?
שאלה: איך .catch() יכול להתעלם משגיאות? איך תמנעו משגיאות לאלץ יציאה מוקדמת מ-Promise.all?
#4 הוסיפו בהירות עם פונקציות בעלות שם 🦄✨
השוו את קריאות 2 הדוגמאות הבאות:
אנונימיות: ❌
Promise.resolve(10) // 10 .then(x => x * 2) // 20 .then(x => x / 4) // 5 .then(x => x * x) // 25 .then(x => x.toFixed(2)) // "25.00" .then(x => console.log(x)) // expected output: "25.00"בעלות שם: ✅
Promise.resolve(10) // 10 .then(double) // 20 .then(quarter) // 5 .then(square) // 25 .then(format) // "25.00" .then(log) // expected output: "25.00"
const double = x => x * 2const quarter = x => x / 4const square = x => x * xconst format = x => x.toFixed(2)const log = x => console.log(x)בונוס: ✅
תואם למתודות מערך!!!
אפשר לעשות שימוש חוזר בפונקציות בעלות השם שלכם עם החברים מ-Array.prototype. כולל .map(), .filter(), .every(), .some(), .find()!
צינורות אוסף #מנצחים:
// IT'S LIKE THE SAME THING :mindblown:
[10, 20] // [ 10, 20 ] .map(double) // [ 20, 40 ] .map(quarter) // [ 5, 10 ] .map(square) // [ 25, 100 ] .map(format) // [ "25.00", "100.00" ] .map(log) // expected 2 lines of output: "25.00", "100.00"ואם אתם לא רוצים לכתוב קוד בסגנון ליניארי כזה… ובכן, יש לכם פונקציות פשוטות!
אתם יכולים להשתמש בהן איך שתרצו:
// Nesting patern// ❌ please don't do this, however
const result = format(square(quarter(double(10))))
log(result)// expected output: "25.00"למה קינון פונקציות הוא אנטי-דפוס?
- לא קריא להרבה אנשים
- diffs של git לא חושפים בקלות מי שינה מה
- קשה לנפות באגים או לרשום לוג מאמצע הפונקציות המקוננות