תכנות אסינכרוני
JavaScript היא שפה single-threaded, אבל יכולה לבצע פעולות ברקע בזכות מנגנון האסינכרוניות.
למה צריך קוד אסינכרוני?
פעולות מסוימות לוקחות זמן: - קריאות לשרת (API) - קריאת קבצים - טיימרים - אנימציות
בלי אסינכרוניות, הדף היה "נתקע" עד שהפעולה מסתיימת.
// ❌ קוד סינכרוני שנתקע
console.log("התחלה");
// ...המתנה של 3 שניות...
console.log("סוף");
// ✅ קוד אסינכרוני - הדף ממשיך לעבוד
console.log("התחלה");
setTimeout(() => {
console.log("אחרי 3 שניות");
}, 3000);
console.log("ממשיך מיד");
setTimeout ו-setInterval
setTimeout - הרצה אחרי זמן
console.log("התחלה");
setTimeout(() => {
console.log("עברו 2 שניות");
}, 2000); // 2000 מילישניות = 2 שניות
console.log("ממשיך");
// פלט: "התחלה", "ממשיך", "עברו 2 שניות"
ביטול setTimeout
const timerId = setTimeout(() => {
console.log("זה לא ירוץ");
}, 5000);
// ביטול
clearTimeout(timerId);
setInterval - הרצה חוזרת
let count = 0;
const intervalId = setInterval(() => {
count++;
console.log(`פעם ${count}`);
if (count >= 5) {
clearInterval(intervalId); // עצירה
}
}, 1000); // כל שנייה
Callbacks
Callback היא פונקציה שמועברת כפרמטר ותקרא כשהפעולה מסתיימת:
function fetchData(callback) {
setTimeout(() => {
const data = { name: "דן", age: 25 };
callback(data); // קריאה ל-callback עם הנתונים
}, 2000);
}
fetchData((result) => {
console.log("קיבלתי:", result);
});
Callback Hell (הבעיה)
// ❌ קשה לקריאה ותחזוקה
getData((data) => {
processData(data, (processed) => {
saveData(processed, (saved) => {
notifyUser(saved, (notification) => {
console.log("סיום!");
});
});
});
});
Promises
Promise הוא אובייקט שמייצג ערך עתידי - הוא יכול להצליח (resolve) או להיכשל (reject).
יצירת Promise
const myPromise = new Promise((resolve, reject) => {
// פעולה אסינכרונית
setTimeout(() => {
const success = true;
if (success) {
resolve("הצלחה!"); // מחזיר ערך
} else {
reject("נכשל!"); // מחזיר שגיאה
}
}, 2000);
});
שימוש ב-Promise
myPromise
.then((result) => {
console.log("הצלחה:", result);
})
.catch((error) => {
console.log("שגיאה:", error);
})
.finally(() => {
console.log("תמיד רץ - הצלחה או כישלון");
});
שרשור Promises
fetchUser(userId)
.then((user) => {
console.log("משתמש:", user.name);
return fetchPosts(user.id);
})
.then((posts) => {
console.log("פוסטים:", posts.length);
return fetchComments(posts[0].id);
})
.then((comments) => {
console.log("תגובות:", comments.length);
})
.catch((error) => {
console.error("שגיאה בשרשרת:", error);
});
async/await
תחביר מודרני ונקי יותר לעבודה עם Promises:
פונקציה async
async function getData() {
return "נתונים"; // מחזיר Promise אוטומטית
}
getData().then(result => console.log(result)); // "נתונים"
await - המתנה ל-Promise
async function fetchData() {
console.log("מתחיל...");
const response = await fetch("https://api.example.com/data");
const data = await response.json();
console.log("קיבלתי:", data);
return data;
}
fetchData();
טיפול בשגיאות
async function fetchData() {
try {
const response = await fetch("https://api.example.com/data");
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
return data;
} catch (error) {
console.error("שגיאה:", error.message);
return null;
}
}
השוואה: Promises vs async/await
// עם Promises
function getUser() {
return fetch("/api/user")
.then(response => response.json())
.then(user => {
console.log(user);
return user;
})
.catch(error => console.error(error));
}
// עם async/await (נקי יותר)
async function getUser() {
try {
const response = await fetch("/api/user");
const user = await response.json();
console.log(user);
return user;
} catch (error) {
console.error(error);
}
}
Promise.all
הרצת מספר Promises במקביל:
async function fetchAllData() {
try {
// כל הבקשות יוצאות במקביל
const [users, posts, comments] = await Promise.all([
fetch("/api/users").then(r => r.json()),
fetch("/api/posts").then(r => r.json()),
fetch("/api/comments").then(r => r.json())
]);
console.log("משתמשים:", users.length);
console.log("פוסטים:", posts.length);
console.log("תגובות:", comments.length);
} catch (error) {
// אם אחת נכשלת - הכל נכשל
console.error("שגיאה:", error);
}
}
Promise.race
מחזיר את התוצאה הראשונה (הצלחה או כישלון):
async function fetchWithTimeout(url, timeout = 5000) {
const timeoutPromise = new Promise((_, reject) => {
setTimeout(() => reject(new Error("Timeout!")), timeout);
});
const fetchPromise = fetch(url);
return Promise.race([fetchPromise, timeoutPromise]);
}
Promise.allSettled
ממתין לכולם, גם אם חלק נכשלים:
const promises = [
fetch("/api/users"),
fetch("/api/posts"),
fetch("/api/bad-url") // יכשל
];
const results = await Promise.allSettled(promises);
results.forEach((result, index) => {
if (result.status === "fulfilled") {
console.log(`#${index} הצליח:`, result.value);
} else {
console.log(`#${index} נכשל:`, result.reason);
}
});
דוגמאות מעשיות
טעינת נתונים עם Loading
async function loadUsers() {
const loadingEl = document.querySelector("#loading");
const listEl = document.querySelector("#userList");
loadingEl.style.display = "block";
try {
const response = await fetch("/api/users");
const users = await response.json();
listEl.innerHTML = users
.map(user => `<li>${user.name}</li>`)
.join("");
} catch (error) {
listEl.innerHTML = "<li>שגיאה בטעינה</li>";
} finally {
loadingEl.style.display = "none";
}
}
Debounce - עיכוב הרצה
function debounce(func, delay) {
let timeoutId;
return function(...args) {
clearTimeout(timeoutId);
timeoutId = setTimeout(() => {
func.apply(this, args);
}, delay);
};
}
// שימוש - חיפוש שמחכה שהמשתמש יפסיק להקליד
const searchInput = document.querySelector("#search");
const search = debounce((query) => {
console.log("מחפש:", query);
// קריאה לAPI
}, 300);
searchInput.addEventListener("input", (e) => {
search(e.target.value);
});
Retry - ניסיון חוזר
async function fetchWithRetry(url, retries = 3) {
for (let i = 0; i < retries; i++) {
try {
const response = await fetch(url);
if (response.ok) {
return await response.json();
}
} catch (error) {
console.log(`ניסיון ${i + 1} נכשל`);
if (i === retries - 1) {
throw error;
}
// המתנה לפני ניסיון נוסף
await new Promise(r => setTimeout(r, 1000));
}
}
}
Event Loop (איך זה עובד)
┌───────────────────────────┐
│ Call Stack │ ← JavaScript רץ כאן
└───────────────────────────┘
↓
┌───────────────────────────┐
│ Web APIs │ ← setTimeout, fetch, etc.
│ (מנוהל ע"י הדפדפן) │
└───────────────────────────┘
↓
┌───────────────────────────┐
│ Callback Queue │ ← callbacks ממתינים
└───────────────────────────┘
↓
┌───────────────────────────┐
│ Event Loop │ ← מעביר לStack כשפנוי
└───────────────────────────┘
console.log("1");
setTimeout(() => {
console.log("2");
}, 0);
Promise.resolve().then(() => {
console.log("3");
});
console.log("4");
// פלט: 1, 4, 3, 2
// (Microtasks/Promises קודמים ל-setTimeout)
סיכום
| מושג | תיאור |
|---|---|
| Callback | פונקציה שתקרא בסיום פעולה |
| Promise | אובייקט שמייצג ערך עתידי |
| async | מגדיר פונקציה אסינכרונית |
| await | ממתין ל-Promise |
| try/catch | טיפול בשגיאות |
| Promise.all | הרצה במקביל, כולם חייבים להצליח |
| Promise.race | הראשון שמסתיים |
| Promise.allSettled | כולם, גם עם כישלונות |
כללי אצבע:
- העדיפו async/await על פני .then()
- תמיד טפלו בשגיאות עם try/catch
- השתמשו ב-Promise.all לבקשות מקביליות
- הימנעו מ-callback hell
בדקו את עצמכם
נסו לענות לבד לפני שאתם פותחים את התשובה.
-
מה מחזירה פונקציה שמוגדרת
async?הצגת התשובה
תשובה ב. גם return 5 בפונקציה async מחזיר Promise שמתממש ל-5.
-
איך תופסים שגיאה של
await?הצגת התשובה
תשובה ג. Promise שנדחה זורק שגיאה בנקודת ה-await, ו-try/catch תופס אותה.
-
מה עושה
Promise.all([a, b])?הצגת התשובה
תשובה ג. מתאים לבקשות בלתי תלויות, כדי לא לחכות להן אחת אחרי השנייה.