4.5 פרומיסים ו async await הרצאה
פרומיסים ו-async/await¶
ג׳אווהסקריפט היא שפה אסינכרונית - פעולות כמו קריאות רשת, קריאת קבצים וטיימרים לא חוסמות את הקוד. במקום לחכות שפעולה תסתיים, JS ממשיכה להריץ את הקוד הבא ומטפלת בתוצאה כשהיא מגיעה.
נלמד שלוש גישות לטיפול בקוד אסינכרוני: callbacks, Promises, ו-async/await.
קולבקים - callbacks¶
הגישה הישנה ביותר. פונקציה מקבלת פונקציה אחרת כפרמטר, וקוראת לה כשהפעולה מסתיימת:
function fetchData(url, callback) {
// simulate async operation
setTimeout(() => {
const data = { name: "Alice", age: 25 };
callback(null, data); // convention: first arg is error, second is result
}, 1000);
}
fetchData("/api/user", (error, data) => {
if (error) {
console.error("Error:", error);
return;
}
console.log("Data:", data);
});
console.log("This runs first!"); // runs before the callback
הבעיה - callback hell¶
כשיש כמה פעולות אסינכרוניות שתלויות זו בזו, הקוד הופך למפלצת של קינונים:
// callback hell / pyramid of doom
getUser(userId, (err, user) => {
if (err) { handleError(err); return; }
getOrders(user.id, (err, orders) => {
if (err) { handleError(err); return; }
getOrderDetails(orders[0].id, (err, details) => {
if (err) { handleError(err); return; }
getShippingStatus(details.shippingId, (err, status) => {
if (err) { handleError(err); return; }
console.log("Status:", status);
});
});
});
});
זה קשה לקריאה, לתחזוקה ולטיפול בשגיאות. Promises פותרים את הבעיה הזו.
פרומיס - Promise¶
Promise הוא אובייקט שמייצג תוצאה עתידית - ערך שיהיה זמין בעתיד, או שגיאה.
לכל Promise יש שלושה מצבים:
- pending - ממתין (עדיין לא הסתיים)
- fulfilled - הצליח (יש תוצאה)
- rejected - נכשל (יש שגיאה)
יצירת Promise¶
const promise = new Promise((resolve, reject) => {
// async operation
setTimeout(() => {
const success = true;
if (success) {
resolve("Data loaded!"); // fulfilled
} else {
reject(new Error("Failed to load")); // rejected
}
}, 1000);
});
שימוש עם then, catch, finally¶
promise
.then((result) => {
console.log("Success:", result); // "Data loaded!"
})
.catch((error) => {
console.error("Error:", error.message);
})
.finally(() => {
console.log("Done (success or failure)");
});
then- רץ כש-Promise מצליחcatch- רץ כש-Promise נכשלfinally- רץ תמיד, בהצלחה ובכישלון
שרשור - chaining¶
כל then מחזיר Promise חדש, מה שמאפשר שרשור:
fetch("/api/user")
.then((response) => {
return response.json(); // returns a Promise
})
.then((user) => {
console.log("User:", user.name);
return fetch(`/api/orders/${user.id}`);
})
.then((response) => {
return response.json();
})
.then((orders) => {
console.log("Orders:", orders);
})
.catch((error) => {
// catches errors from ANY step above
console.error("Error:", error.message);
});
השוו את זה ל-callback hell - הקוד שטוח וקריא הרבה יותר. ה-catch בסוף תופס שגיאות מכל השלבים.
מתודות סטטיות של Promise¶
Promise.all - כולם חייבים להצליח¶
מחכה שכל ה-Promises יסתיימו. אם אחד נכשל - הכל נכשל:
const promise1 = fetch("/api/users");
const promise2 = fetch("/api/posts");
const promise3 = fetch("/api/comments");
Promise.all([promise1, promise2, promise3])
.then(([usersRes, postsRes, commentsRes]) => {
// all three succeeded
console.log("All loaded!");
})
.catch((error) => {
// one of them failed
console.error("One failed:", error);
});
שימושי כשצריך כמה פעולות ביחד וכולן נדרשות.
Promise.allSettled - מחכה לכולם בלי קשר להצלחה¶
const promises = [
fetch("/api/users"),
fetch("/api/broken-endpoint"),
fetch("/api/posts")
];
Promise.allSettled(promises)
.then((results) => {
results.forEach((result) => {
if (result.status === "fulfilled") {
console.log("Success:", result.value);
} else {
console.log("Failed:", result.reason);
}
});
});
שימושי כשרוצים לדעת מה הצליח ומה נכשל, בלי שכישלון אחד יעצור את השאר.
Promise.race - הראשון שמסתיים¶
מחזיר את התוצאה של ה-Promise הראשון שמסתיים (הצלחה או כישלון):
// timeout pattern
function fetchWithTimeout(url, timeout) {
const fetchPromise = fetch(url);
const timeoutPromise = new Promise((_, reject) => {
setTimeout(() => reject(new Error("Timeout!")), timeout);
});
return Promise.race([fetchPromise, timeoutPromise]);
}
fetchWithTimeout("/api/data", 5000)
.then((response) => console.log("Got response"))
.catch((error) => console.log(error.message)); // "Timeout!" if too slow
Promise.any - הראשון שמצליח¶
כמו race, אבל מתעלם מכישלונות - מחכה להצלחה הראשונה:
// try multiple sources, use whichever responds first
Promise.any([
fetch("https://primary-api.com/data"),
fetch("https://backup-api.com/data"),
fetch("https://mirror-api.com/data")
])
.then((response) => {
console.log("Got response from fastest source");
})
.catch((error) => {
// only if ALL failed
console.error("All sources failed");
});
סיכום מתודות סטטיות¶
| מתודה | מחכה ל... | נכשל כש... |
|---|---|---|
Promise.all |
כולם יצליחו | אחד נכשל |
Promise.allSettled |
כולם יסתיימו | אף פעם לא נכשל |
Promise.race |
הראשון שמסתיים | הראשון שנכשל (אם הוא ראשון) |
Promise.any |
הראשון שמצליח | כולם נכשלים |
async/await¶
תחביר מודרני שהופך קוד אסינכרוני להיראות כמו קוד סינכרוני. async/await בנוי מעל Promises - זה syntactic sugar.
// with Promises:
function getUser() {
return fetch("/api/user")
.then((response) => response.json())
.then((user) => {
console.log(user.name);
return user;
});
}
// with async/await - same thing, cleaner:
async function getUser() {
const response = await fetch("/api/user");
const user = await response.json();
console.log(user.name);
return user;
}
asyncלפני פונקציה - הפונקציה תמיד מחזירה Promiseawaitלפני ביטוי - מחכה ש-Promise יסתיים ומחזיר את הערךawaitאפשר להשתמש רק בתוך פונקציהasync(או ב-top level של מודול)
טיפול בשגיאות - try/catch¶
עם async/await, תופסים שגיאות עם try/catch רגיל:
async function fetchUserData(userId) {
try {
const response = await fetch(`/api/users/${userId}`);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const user = await response.json();
return user;
} catch (error) {
console.error("Failed to fetch user:", error.message);
return null;
} finally {
console.log("Fetch attempt complete");
}
}
const user = await fetchUserData(1);
אפשר גם try/catch ממוקד - לתפוס שגיאות רק ממקומות ספציפיים:
async function processData() {
let response;
try {
response = await fetch("/api/data");
} catch (error) {
console.error("Network error:", error);
return;
}
let data;
try {
data = await response.json();
} catch (error) {
console.error("Invalid JSON:", error);
return;
}
// process data...
console.log("Data:", data);
}
ריצה מקבילית מול ריצה סדרתית¶
סדרתי - sequential (איטי)¶
כל פעולה מחכה שהקודמת תסתיים:
async function sequential() {
const user = await fetchUser(1); // waits 1 second
const posts = await fetchPosts(user.id); // then waits 1 second
const comments = await fetchComments(); // then waits 1 second
// total: ~3 seconds
}
זה הגיוני כשכל שלב תלוי בקודם (צריך את ה-user כדי לבקש את ה-posts שלו).
מקבילי - parallel (מהיר)¶
כשהפעולות עצמאיות, אפשר להריץ אותן במקביל:
async function parallel() {
const [user, posts, comments] = await Promise.all([
fetchUser(1),
fetchPosts(),
fetchComments()
]);
// total: ~1 second (the slowest one)
}
כלל חשוב: אם הפעולות לא תלויות זו בזו, השתמשו ב-Promise.all להריצה מקבילית.
דפוס מעורב¶
לפעמים חלק מהפעולות תלויות וחלק לא:
async function mixed() {
// step 1: get user (must be first)
const user = await fetchUser(1);
// step 2: get posts and friends in parallel (both depend on user, not on each other)
const [posts, friends] = await Promise.all([
fetchPosts(user.id),
fetchFriends(user.id)
]);
console.log(user, posts, friends);
}
דפוסים נפוצים¶
עיבוד מערך באופן סדרתי¶
// process items one by one (sequential)
async function processSequentially(urls) {
const results = [];
for (const url of urls) {
const response = await fetch(url);
const data = await response.json();
results.push(data);
}
return results;
}
עיבוד מערך באופן מקבילי¶
// process all items at once (parallel)
async function processInParallel(urls) {
const promises = urls.map(async (url) => {
const response = await fetch(url);
return response.json();
});
return Promise.all(promises);
}
retry - ניסיון חוזר¶
async function fetchWithRetry(url, maxRetries = 3) {
for (let attempt = 1; attempt <= maxRetries; attempt++) {
try {
const response = await fetch(url);
if (!response.ok) throw new Error(`HTTP ${response.status}`);
return await response.json();
} catch (error) {
console.log(`Attempt ${attempt} failed: ${error.message}`);
if (attempt === maxRetries) throw error;
// wait before retrying (exponential backoff)
await new Promise(resolve =>
setTimeout(resolve, 1000 * attempt)
);
}
}
}
המרה מ-callback ל-Promise¶
// wrap a callback-based function in a Promise
function delay(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
// usage
async function example() {
console.log("Start");
await delay(2000);
console.log("2 seconds later");
}
async/await עם לולאות¶
שימו לב ש-await לא עובד כצפוי בתוך forEach:
// BUG: forEach doesn't wait for async callbacks
const urls = ["/api/1", "/api/2", "/api/3"];
urls.forEach(async (url) => {
const response = await fetch(url);
const data = await response.json();
console.log(data); // these run in parallel, not sequential!
});
console.log("Done"); // this runs before the fetches complete!
הפתרון - השתמשו ב-for...of לריצה סדרתית:
// FIXED: for...of waits correctly
for (const url of urls) {
const response = await fetch(url);
const data = await response.json();
console.log(data);
}
console.log("Done"); // runs after all fetches complete
או Promise.all עם map לריצה מקבילית:
// parallel with map + Promise.all
const results = await Promise.all(
urls.map(async (url) => {
const response = await fetch(url);
return response.json();
})
);
console.log("All done:", results);
השוואה לפייתון¶
פייתון תומך ב-async/await עם asyncio:
# import asyncio
#
# async def fetch_user(user_id):
# await asyncio.sleep(1) # simulate async work
# return {"id": user_id, "name": "Alice"}
#
# async def main():
# user = await fetch_user(1)
# print(user)
#
# asyncio.run(main())
| נושא | פייתון | ג׳אווהסקריפט |
|---|---|---|
| הגדרה | async def func(): |
async function func() {} |
| המתנה | await expr |
await expr |
| הרצה מקבילית | asyncio.gather() |
Promise.all() |
| לולאת אירועים | צריך asyncio.run() |
מובנית בסביבה |
| Promises | asyncio.Future (פחות נפוץ) |
Promise (בסיס השפה) |
ההבדל המרכזי: ב-JS כל דבר אסינכרוני בנוי סביב Promises ולולאת האירועים רצה תמיד. בפייתון, asyncio הוא תוספת שצריך להפעיל מפורשות. בפרקטיקה, קוד אסינכרוני הרבה יותר נפוץ ב-JS מאשר בפייתון.
סיכום¶
- callbacks - הגישה הישנה, callback hell הוא הבעיה
- Promises - אובייקט שמייצג תוצאה עתידית, then/catch/finally
- שרשור - כל
thenמחזיר Promise חדש - Promise.all - מחכה לכולם (נכשל אם אחד נכשל)
- Promise.allSettled - מחכה לכולם (מדווח על כל אחד)
- Promise.race - הראשון שמסתיים
- Promise.any - הראשון שמצליח
- async/await - syntactic sugar מעל Promises, קוד נקי יותר
- try/catch - טיפול בשגיאות עם async/await
- ריצה מקבילית - השתמשו ב-
Promise.allכשהפעולות עצמאיות - ריצה סדרתית -
for...ofעםawait(לאforEach) - retry, delay, timeout - דפוסים נפוצים שחשוב להכיר