תכנות אסינכרוני

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

בדקו את עצמכם

נסו לענות לבד לפני שאתם פותחים את התשובה.

  1. מה מחזירה פונקציה שמוגדרת async?

    1. Callback
    2. תמיד Promise
    3. את הערך ישירות
    4. תמיד undefined
    הצגת התשובה

    תשובה ב. גם return 5 בפונקציה async מחזיר Promise שמתממש ל-5.

  2. איך תופסים שגיאה של await?

    1. אי אפשר
    2. עם else
    3. עם try/catch סביב ה-await
    4. עם if
    הצגת התשובה

    תשובה ג. Promise שנדחה זורק שגיאה בנקודת ה-await, ו-try/catch תופס אותה.

  3. מה עושה Promise.all([a, b])?

    1. מבטל את שניהם
    2. מחזיר את הראשון שמסתיים
    3. מחכה לשניהם במקביל, ונכשל אם אחד נכשל
    4. מריץ את a ואז את b
    הצגת התשובה

    תשובה ג. מתאים לבקשות בלתי תלויות, כדי לא לחכות להן אחת אחרי השנייה.