Menu

async/await ב-JavaScript: קוד אסינכרוני שנקרא מלמעלה למטה

איך async/await באמת עובד ב-JavaScript: פונקציות async, המתנה ל-promises, טיפול בשגיאות עם try/catch, והרצת משימות במקביל.

בדף הזה יש עורכים שאפשר להריץ - לערוך, להריץ ולראות את הפלט מיד.

async/await הוא פשוט promises בתחפושת

async/await הוא לא מודל מקביליות חדש. זו שכבת תחביר מעל promises שמאפשרת לכתוב קוד ש_נראה_ סדרתי גם כשהוא אסינכרוני. אותו מנגנון, צורה הרבה יותר ידידותית.

הנה אותה משימה כתובה בשתי הדרכים:

שתי הפונקציות מחזירות promise. שתיהן עושות בדיוק את אותו הדבר. גרסת ה-async נקראת מלמעלה למטה בלי שרשרת .then, וזה כל הקסם.

async מסמן שפונקציה מחזירה promise

שימו async לפני function, לפני arrow function או לפני מתודה, ושני דברים קורים:

  1. הפונקציה תמיד מחזירה promise. כל מה שעושים לו return הופך לערך שה-promise מסתיים בו.
  2. מותר להשתמש בתוכה ב-await.

שימו לב ש-result הוא לא המחרוזת: הוא promise שמסתיים במחרוזת. אף ש-greet לא מכילה await ולא עבודה אסינכרונית, מילת המפתח async עדיין עוטפת את ערך ההחזרה ב-promise. אם הפונקציה זורקת שגיאה, ה-promise נדחה.

await עוצר עד שה-promise מסתיים

בתוך פונקציית async, await somePromise עוצר את הפונקציה עד שה-promise מתממש, ואז מוסר לכם את הערך. אם ה-promise נדחה, await זורק שגיאה.

שימו לב לסדר הפלט. "started countdown" מודפס לפני "2", כי await עוצר רק את פונקציית ה-async, לא את שאר התוכנית. ה-event loop ממשיך לרוץ; countdown פשוט ממשיכה מאוחר יותר, כשכל promise של wait מתממש.

אפשר לעשות await לכל דבר שדומה ל-promise. גם await 42 חוקי: ערכים שאינם promises נעטפים ב-Promise.resolve(42) ומתממשים מיד.

טיפול בשגיאות עם try/catch

עם promises רגילים משרשרים .catch(). עם async/await, promise שנדחה הופך לחריגה שנזרקת, ואפשר לתפוס אותה בדרך הרגילה:

try/catch אחד מכסה כל await שבתוכו. תקלות רשת, שגיאות בפענוח JSON ופקודות throw שלכם, כולן נוחתות באותו catch. זה שדרוג אמיתי לעומת שרשראות .then/.catch מקוננות.

דבר אחד לשים לב אליו: fetch נדחה רק בשגיאות רשת, לא ב-HTTP 4xx/5xx. אתם בודקים את res.ok בעצמכם וזורקים שגיאה, דפוס שתראו כל הזמן בקוד אמיתי.

אל תעשו await בתוך לולאה כשאין צורך

זו המלכודת הנפוצה ביותר של async/await. await סדרתי בתוך לולאה אומר שכל איטרציה מחכה לקודמת:

sequential לוקחת בערך 900ms. parallel לוקחת בערך 300ms. כלל האצבע: אם המשימות לא תלויות זו בתוצאות של זו, התחילו את כולן ואז עשו await Promise.all. עשו await אחת אחרי השנייה רק כשהקריאה הבאה באמת צריכה את התוצאה הקודמת.

לאוספים, Promise.all(items.map(async (x) => ...)) הוא הניב המקובל. for...of רגילה עם await בפנים רצה באופן סדרתי: לפעמים זה מה שרוצים (הגבלת קצב, שמירה על סדר), אבל בדרך כלל לא.

שילוב של async/await עם promises רגילים

לא חייבים לבחור צד. פונקציות async מחזירות promises, ו-await עובד על כל promise, כך שאפשר לשלב ביניהם בחופשיות:

שני הסגנונות ניתנים להחלפה. השתמשו ב-await כשהקוד נקרא טוב יותר מלמעלה למטה; השתמשו ב-.then כשרוצים משהו חד פעמי מהיר או כשעובדים מחוץ להקשר אסינכרוני.

await ברמה העליונה (ב-ES Modules)

בעבר היה צריך לעטוף await בפונקציית async, כי await לא היה מותר ברמה העליונה של סקריפט. זה השתנה: בתוך ES module (קובץ .mjs או <script type="module">), אפשר היום לעשות await ישירות ברמה העליונה:

// in an ES module
const res = await fetch("https://jsonplaceholder.typicode.com/users/1");
const user = await res.json();
console.log(user.name);

await ברמה העליונה מעכב את סיום הטעינה של המודול עד שה-promise מסתיים, וכל מי שמייבא את המודול מחכה גם הוא. זה נוח לטעינת קונפיגורציה ולייבוא דינמי, אבל השתמשו בזה במשורה: await איטי ברמה העליונה חוסם את כל מי שמייבא את המודול.

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

דברים שמכשילים אנשים

סיור מהיר במלכודות הרגילות:

  • שוכחים async. שימוש ב-await בפונקציה רגילה הוא שגיאת תחביר. הפתרון הוא להוסיף async, או לקרוא לפונקציית העזר האסינכרונית עם .then.
  • שוכחים לעשות await לתוצאה. const data = getJSON(url); נותן לכם promise, לא את הנתונים. אם תשתמשו בו כאילו הוא הערך, תראו [object Promise] מופיע בפלט.
  • דחיות בלי טיפול. פונקציה אסינכרונית שמפעילים ושוכחים (doWork();) תבלע שגיאות בשקט, אלא אם מוסיפים .catch או עושים לה await בתוך try/catch.
  • forEach עם callbacks אסינכרוניים. array.forEach(async (x) => await something(x)) לא מחכה לשום דבר: forEach מתעלמת מה-promises שמוחזרים. השתמשו ב-for...of עם await, או ב-Promise.all(array.map(...)).

הריצו את זה: "finished?" מודפס לפני כל "done", כי broken חוזרת בלי לחכות. fixed מחכה לכולם, ורק אז מדפיסה "finished!" אחרונה.

מתי לבחור ב-async/await

ברירת המחדל היא async/await לכל קוד שמבצע יותר מצעד אסינכרוני אחד ברצף, או שצריך טיפול בשגיאות בסגנון try/catch. הישארו עם promises רגילים לשורות בודדות פשוטות, לקוד ספרייה שמחזיר promise בלי לחכות בעצמו לשום דבר, או כשבאמת צריך צירופים כמו Promise.race או .finally() בשרשרת.

כשמשתמשים בו נכון, async/await הופך קוד אסינכרוני לקריא כמו מתכון: עשו את זה, אחר כך את זה, אחר כך את זה. ה-event loop ממשיך לעשות את שלו, אתם רק מפסיקים לחשוב ב-callbacks.

הבא בתור: ה-fetch API

רוב הדוגמאות כאן השתמשו ב-fetch כמייצג של "משהו אסינכרוני". שווה להכיר אותו כמו שצריך: איך בקשות ותגובות עובדות, טיפול ב-JSON, הגדרת headers, ולמה fetch לא נדחה בשגיאות HTTP. זה העמוד הבא.

שאלות נפוצות

מה עושה async/await ב-JavaScript?

async/await הוא תחביר לעבודה עם promises שמאפשר לכתוב קוד אסינכרוני כאילו הוא סינכרוני. async מסמן שפונקציה מחזירה promise, ו-await עוצר בתוך הפונקציה הזו עד שה-promise מסתיים, ואז נותן לכם את הערך שהתקבל. מתחת לפני השטח אלה עדיין promises, רק קלים יותר לקריאה.

אפשר להשתמש ב-await מחוץ לפונקציה async?

ברמה העליונה של ES module, כן: זה נקרא await ברמה העליונה. בתוך פונקציות רגילות או בסקריפטים של CommonJS, לא: await מחוץ לפונקציית async הוא שגיאת תחביר. הפתרון הוא בדרך כלל לעטוף את הקוד בפונקציית async ולקרוא לה, או להפוך את הקובץ ל-ES module.

איך מטפלים בשגיאות עם async/await?

עטפו את הקריאות שממתינים להן ב-try/catch. כל promise שנדחה ושעושים לו await הופך לחריגה שנזרקת, ובלוק ה-catch יכול לטפל בה. למשימות רקע שלא עושים להן await, הוסיפו .catch() ל-promise המוחזר כדי שדחיות לא יישארו בלי טיפול.

האם await חוסם את כל התוכנית?

לא. await עוצר רק את פונקציית ה-async הנוכחית. ה-event loop ממשיך לרוץ: טיימרים מופעלים, משימות אסינכרוניות אחרות מתקדמות, והממשק נשאר תגובתי. הקוד הקורא מקבל בחזרה promise ממתין וממשיך מיד.

איור של שפות התכנות ב-Coddy

ללמוד תכנות עם Coddy

להתחיל