Event Loop

Memory

  • Two important components are:
    • Heap
    • Call Stack

Event Loop Flow

event_loop

  • Every function call goes to call stack
  • If the function is asynchronous and need time to finish, It is sent to <>
  • After the async function finishes, the result is pushed to callback queue (aka message queue)
  • The event loop constantly checks if the call stack is empty:
    • If the call stack is not empty, it waits
    • If the call stack is empty, it pushes the result from callback queue to call stack
  • Notes:
    • V8 engine doesn’t have setTimeout etc.
    • Web APIs are extra stuff provided by browser

Animation Example

Macrotask queue (or simply Callback Queue or Task Queue in Browser)

  • To schedule a new macrotask: use zero delayed setTimeout(f).
  • The following goes to macrotask queue:
    • setTimeout, setInterval, setImmediate
    • requestAnimationFrame
    • I/O
    • UI rendering
    • script tag, script execution

Microtask queue (aka Jobs in Browser)

  • There’s no UI or network event handling between microtasks: they run immediately one after another.
  • One micro task can schedule another micro task. Event loop continuously runs all the micro task.
  • To schedule a new microtask, use queueMicrotask(f).
  • The following goes to microtask queue:
    • Promise callbacks
    • process.nextTick
    • queueMicrotask

Simplified Event loop algo

  1. Dequeue and run the oldest task from the macrotask queue (e.g. “script” execution).
  2. Execute all microtasks:
    • While the microtask queue is not empty:
      • Dequeue and run the oldest microtask.
  3. Render changes if any.
  4. If the macrotask queue is empty, wait till a macrotask appears.
  5. Go to step 1.

Example

console.log(1);
// The first line executes immediately, it outputs `1`.
// Macrotask and microtask queues are empty, as of now.
 
setTimeout(() => console.log(2));
// `setTimeout` appends the callback to the macrotask queue.
// - macrotask queue content:
//   `console.log(2)`
 
Promise.resolve().then(() => console.log(3)).then(() => console.log(3.1));
// The callback is appended to the microtask queue.
// - microtask queue content:
//   `console.log(3)`
 
Promise.resolve().then(() => setTimeout(() => console.log(4)));
// The callback with `setTimeout(...4)` is appended to microtasks
// - microtask queue content:
//   `console.log(3); setTimeout(...4)`
 
Promise.resolve().then(() => console.log(5));
// The callback is appended to the microtask queue
// - microtask queue content:
//   `console.log(3); setTimeout(...4); console.log(5)`
 
setTimeout(() => console.log(6));
// `setTimeout` appends the callback to macrotasks
// - macrotask queue content:
//   `console.log(2); console.log(6)`
 
console.log(7);
// Outputs 7 immediately.
  • The whole script will run first which is a macrotask: 1, 7
  • Then all the microtasks are executed: 3, 5 and 3.1 is scheduled as microtask since it is a settled promise
    • 3.1 will execute
  • Then next macrotask: 2
  • Then all the microtasks: None
  • Then next macrotask: 6
  • Then all the microtasks: None
  • Then next macrotask: 4
  • Output: 1, 7, 3, 5, 3.1, 2, 6, 4