JavaScript ופיתוח ווב
בני נוער ומתחילים

Event Loop ב-JavaScript לנוער: למה setTimeout(0) מחכה בתור?

שלוש פקודות נכנסות לקונסול: אחת רגילה, אחת בתוך Timer ואחת בתוך Promise. כולן קצרות, אבל הן אינן מסיימות לפי סדר הכתיבה. במקום לשנן תשובה, נבנה מודל שמאפשר לחזות מי רץ עכשיו ומי מחכה בתור.

עודכן: יולי 2026
כ-4 דקות קריאה
תזמון קוד וניסוי בקונסול
חיזוי לפני הרצה
console.log("first")

setTimeout(() => {
  console.log("timer")
}, 0)

console.log("last")
output 1

first

output 2

last

output 3

timer

החידה שמגלה את המודל

Timer מוכן אינו Timer שרץ

הערך 0 אינו הוראה לדלג על שאר הקוד. הוא אומר לדפדפן שלא נדרשת השהיה מכוונת ארוכה יותר לפני שה-callback יכול להיות מועמד להרצה. אם המחסנית עסוקה, הוא מחכה.

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

כלל ראשון למעבדה

לפני כל הרצה כותבים תחזית. רק אחר כך פותחים את הקונסול ומשווים בין המודל לבין התוצאה.

מפת ההרצה

ארבע תחנות, לא ״קסם אסינכרוני״

הביטוי Event Loop מתאר את הבדיקה החוזרת שמחברת בין הקוד שרץ כעת לבין העבודה שמוכנה להמשיך. כדי לעקוב אחרי הסדר, מספיק לדעת באיזו תחנה נמצאת כל פעולה.

  1. תחנה 1

    הקוד הסינכרוני

    Call Stack

    JavaScript מריץ פקודה אחת בכל פעם. פונקציה שנקראה נכנסת למחסנית ויוצאת ממנה כשהיא מסיימת.

  2. תחנה 2

    הדפדפן מטפל בהמתנה

    Web APIs

    Timer, אירוע לחיצה או בקשת רשת יכולים להמתין מחוץ למחסנית. ההמתנה אינה עוצרת את שאר הקוד.

  3. תחנה 3

    עבודות דחופות אחרי הקוד

    Microtask Queue

    תגובות של Promise נכנסות לתור המיקרו־משימות. הן מטופלות אחרי שהמחסנית מתרוקנת ולפני המשימה הבאה.

  4. תחנה 4

    המשימה הבאה

    Task Queue

    Callback של Timer מוכן ממתין כאן. ה-Event Loop מעביר אותו למחסנית רק כשהיא פנויה ואחרי המיקרו־משימות.

קוד סינכרוני
Microtasks
Task אחת
וחוזר חלילה
שלושה ניסויי קונסול

תחזית, הרצה, הסבר — בכל סבב

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

LAB 01
console.log("A")

setTimeout(() => {
  console.log("B")
}, 0)

console.log("C")

אפס מילישניות עדיין אומר ״אחר כך״

באיזה סדר יודפסו A, B ו-C?

התוצאה

A → C → B

A ו-C הם קוד סינכרוני ולכן הם מסיימים קודם. ה-Timer יכול להיות מוכן כמעט מיד, אך ה-callback שלו עדיין ממתין לתורו.

LAB 02
console.log("A")

setTimeout(() => console.log("B"), 0)

Promise.resolve().then(() => {
  console.log("C")
})

console.log("D")

Promise נכנס למסלול קצר יותר

מי יודפס קודם: B או C?

התוצאה

A → D → C → B

אחרי A ו-D המחסנית ריקה. כעת JavaScript מרוקן את תור המיקרו־משימות, ולכן C של ה-Promise מופיע לפני B של ה-Timer.

LAB 03
setTimeout(() => {
  console.log("timer 1")

  Promise.resolve().then(() => {
    console.log("microtask")
  })
}, 0)

setTimeout(() => {
  console.log("timer 2")
}, 0)

גם בין שתי משימות בודקים Microtasks

האם timer 2 עוקף את ה-Promise שנוצר ב-timer 1?

התוצאה

timer 1 → microtask → timer 2

המשימה הראשונה מסתיימת ואז המיקרו־משימה שנוצרה בתוכה מטופלת. רק לאחר מכן עוברים למשימת ה-Timer הבאה.

מיתוס מול בדיקה

שלושה משפטים שנשמעים הגיוניים — ומטעים

במקום להחליף מיתוס בסיסמה חדשה, לכל תיקון מצורף ניסוי קטן שמראה מה באמת קובע את הסדר.

״setTimeout(fn, 0) מפעיל את הפונקציה מיד״

המספר מציין זמן המתנה מינימלי לפני שה-callback יכול להיכנס לתור. הוא אינו קובע מתי המחסנית תהיה פנויה.

הבדיקה: מוסיפים console.log מיד אחרי ה-setTimeout ובודקים מי מודפס קודם.

״Promise תמיד מהיר יותר מ-Timer״

לא מדובר במרוץ מהירות אלא בכללי תזמון שונים. callback של Promise הוא Microtask, ואילו callback של Timer הוא Task.

הבדיקה: שמים את שניהם אחרי אותו קטע סינכרוני ומשווים את סדר ההדפסה.

״Event Loop מריץ כמה שורות JavaScript במקביל״

בזרם הראשי, המחסנית מריצה יחידת קוד אחת בכל רגע. הדפדפן יכול לטפל בהמתנות מחוץ למחסנית ולהחזיר callbacks בהמשך.

הבדיקה: מפרידים על הנייר בין ״רץ עכשיו״, ״ממתין בדפדפן״ ו״מוכן בתור״.

אלגוריתם חיזוי

כשהסדר מפתיע, עוברים ארבע בדיקות

  1. 1

    מסמנים כל פעולה

    ליד כל שורה כותבים: סינכרונית, Timer, אירוע, Promise או פעולה אחרת שמחזירה callback.

  2. 2

    עוקבים אחרי המחסנית

    לא קופצים ישר לתורים. קודם מסיימים את כל מה שכבר רץ, כולל פונקציות שקראו לפונקציות נוספות.

  3. 3

    מרוקנים Microtasks

    כשהמחסנית מתרוקנת, בודקים אם Promise הוסיף עבודה. Microtask יכולה ליצור Microtask נוספת באותו סבב.

  4. 4

    לוקחים Task אחת

    רק עכשיו מעבירים callback מוכן של Timer או אירוע למחסנית, ואז חוזרים שוב לאותה בדיקה.

איך נראית הבנה, לא רק תשובה נכונה?

  • התלמיד מסביר היכן כל callback ממתין, ולא רק מקריא את הפלט.
  • הוא משנה שורה אחת ומעדכן את התחזית לפני ההרצה הבאה.
  • הוא יודע לומר מתי המודל הפשוט אינו מספיק וצריך לבדוק תיעוד או התנהגות בדפדפן.

משימת 15 דקות

כותבים חידת תזמון משלכם

התחילו בשתי הדפסות סינכרוניות, הוסיפו Timer ו-Promise אחד, וכתבו את הפלט הצפוי בלי להריץ. לאחר הבדיקה, ציירו ארבע קופסאות: מחסנית, דפדפן, Microtasks ו-Tasks. העבירו ביניהן כל פעולה עד שהסדר ברור.

המטרה אינה לייצר קוד מסובך, אלא ניסוי קטן שאפשר להסביר שורה אחר שורה.

ממשיכים מהתור אל הפרויקט

עכשיו מחפשים את ה-Event Loop בקוד אמיתי

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