הסיבה שאני משתמש ב-async ו-await היא שאלו הופכים קוד אסינכרוני לקריא יותר. זוהי פשוט דרך נוחה יותר לכתוב קוד שמחכה לתשובה, בין אם מבקשה, מקריאה למסד נתונים, או מ-endpoint של WordPress REST API.
הדרך בה אני חושב על זה פשוטה: מאחורי הקלעים עדיין עובדים עם Promises. התחביר של async/await רק מאפשר לכתוב את הקוד לפי אותו הסדר בו הראש שלנו חושב.
מה async עושה
כשאנו מסמנים פונקציה עם async, הפונקציה תמיד מחזירה Promise. גם אם מחזירים ערך רגיל, JavaScript עוטפת אותו עבורכם.
async function getNumber() {
return 42;
}
getNumber().then(function (number) {
console.log(number); // 42
});אם פונקציית async זורקת שגיאה, ה-Promise שהיא מחזירה נכשל. זו הנקודה המנטלית הראשונה שחשוב להבין: ברגע שפונקציה היא async, מי שקורא לה צריך להתייחס אליה כפונקציה שמחזירה Promise.
הצהרה על פונקציה אסינכרונית יוצרת פונקציה שמחזירה Promise חדש, אשר נפתר עם ערך ההחזרה של הפונקציה או נדחה במקרה של חריגה שלא נתפסה. MDN Web Docs
מה await באמת עושה
await משהה את פונקציית ה-async הנוכחית עד שה-Promise מסתיים. הוא לא מקפיא את הדפדפן. הוא לא חוסם את כל ה-thread של JavaScript. שאר העמוד יכול להמשיך לעבוד.
console.log('1');
async function run() {
console.log('2');
const value = await Promise.resolve('done');
console.log('4', value);
}
run();
console.log('3');הפלט יהיה 1, 2, 3, 4 done. הפונקציה נעצרת בשורת ה-await, מחזירה שליטה לשאר התוכנית, וממשיכה מאוחר יותר.
זו הנקודה שהייתי רוצה שיותר מפתחים יבינו מוקדם. await גורם לקוד להיראות לינארי, אבל בזמן הריצה הוא עדיין אסינכרוני.
למה אני מעדיף try/catch
היתרון העיקרי במקרה זה הוא טיפול בשגיאות. שרשראות Promise הן לגמרי תקינות, אך כשיש flow עם כמה שלבים, try/catch פשוט קריא יותר ונוח יותר לדיבוג.
async function loadDashboard(userId) {
try {
const user = await fetchUser(userId);
const orders = await fetchOrders(user.id);
return { user, orders };
} catch (err) {
console.error('Dashboard failed', err);
return { user: null, orders: [] };
}
}זה בעצם כמו ה-flow שיש לי בראש: להביא את המשתמש, להביא את ההזמנות, להחזיר את התוצאה, ולטפל במה שנכשל.
מלכודת הביצועים
הטעות שאני רואה הכי הרבה עם async/await היא קוד סדרתי בטעות: שלוש בקשות עצמאיות שנכתבות כשלוש שורות await נפרדות. זה נראה נקי, אבל גורם לדפדפן לחכות יותר זמן ממה שצריך.
העברתי את הנושא הזה לפוסט קצר וממוקד יותר, עם דוגמאות חיות: Sequential vs Parallel JavaScript: The Performance Mistake.
הגרסה הקצרה: אם פעולה אסינכרונית אחת צריכה את התוצאה של הפעולה הקודמת, השאירו אותה סדרתית. אם הפעולות עצמאיות, התחילו אותן יחד עם Promise.all().
המלכודת של forEach
זו עוד טעות שכדאי לעצור עליה, כי היא נראית תמימה מאוד:
async function saveAll(items) {
items.forEach(async function (item) {
await save(item);
});
console.log('done?');
}forEach לא מחכה ל-callback האסינכרוני. הוא מתעלם מה-Promise שה-callback מחזיר, ולכן done? יכול לרוץ לפני שהשמירות הסתיימו.
השתמשו ב-for...of כשהעבודה חייבת לקרות פריט אחרי פריט:
async function saveAll(items) {
for (const item of items) {
await save(item);
}
console.log('done');
}השתמשו ב-Promise.all() עם map() כשהפריטים יכולים להיות מעובדים יחד:
async function saveAll(items) {
await Promise.all(items.map(function (item) {
return save(item);
}));
console.log('done');
}דוגמה אמיתית מ-WordPress
ב frontend של WordPress אני בדרך כלל רואה async/await סביב ה-REST API. תבנית נפוצה היא טעינת פוסט, ואז טעינת מידע שתלוי בפוסט הזה.
async function loadPostBundle(postId) {
const base = '/wp-json/wp/v2';
try {
const post = await fetch(base + '/posts/' + postId)
.then(function (response) { return response.json(); });
const author = await fetch(base + '/users/' + post.author)
.then(function (response) { return response.json(); });
return { post, author };
} catch (err) {
console.error('Could not load post bundle', err);
return null;
}
}כאן הסדרתיות מכוונת. בקשת המחבר צריכה את post.author, ולכן אני מחכה קודם לפוסט. כשמרנדרים את התוצאה, חשוב לזכור שגם העבודה מול ה-DOM עדיין משפיעה. המדריך על איך להימנע מ-DOM גדול מדי הוא המשך טוב אם אתם בונים ממשקים דינמיים.
טעויות נפוצות
אלו בעיות ה-async/await שאני מחפש ראשון בקוד אמיתי:
- שוכחים
await. מקבלים Promise במקום הערך שציפיתם לקבל. - משתמשים ב-
awaitבתוךforEach. הלולאה לא מחכה. - כותבים בלוקי
catchריקים. הכישלון נעלם עד שמשתמשים מדווחים עליו. - מריצים בקשות עצמאיות בסדרה. לטעות הביצועים הזו יש עכשיו פוסט ממוקד עם דוגמאות חיות.
- מוסיפים
asyncכשאין שוםawait. לפעמים החזרת ה-Promise המקורי ברורה יותר.
שאלות נפוצות
שאלות נפוצות על שימוש ב-async ו-await ב-JavaScript.
async תמיד מחזירה Promise. ערך שמוחזר מהפונקציה הופך ל-Promise שמסתיים בהצלחה, ושגיאה שנזרקת הופכת ל-Promise שנכשל.await משהה את פונקציית ה-async הנוכחית ומחזיר שליטה ל-event loop. הוא לא מקפיא את הדפדפן.try/catch כשאפשר לטפל בכישלון באותה נקודה. אם מי שקורא לפונקציה צריך להחליט מה לעשות, אפשר לתת ל-rejection לעלות למעלה.forEach לא יחכה ל-callback האסינכרוני. השתמשו ב-for...of לעבודה סדרתית או ב-Promise.all() עם map() לעבודה מקבילית.Promise.all() כשכמה פעולות אסינכרוניות עצמאיות אחת מהשנייה ואתם צריכים את כולן לפני שממשיכים.סיכום
async/await הוא לא מנוע אחר מתחת למכסה המנוע. זו דרך ברורה יותר לכתוב קוד מבוסס Promise.
השתמשו ב-async כשפונקציה צריכה להחזיר Promise. השתמשו ב-await כשצריך להשהות את אותה פונקציה עד ש-Promise מסתיים. השתמשו ב-try/catch במקום שבו באמת אפשר לטפל בכישלון. וכשביצועים חשובים, ודאו שאתם לא מחכים בסדרה לעבודה שיכלה להתחיל יחד.

