‹ Home

Tuesday, 26 August 2025

JavaScript Promise

An idiot's overview of JavaScript promises & the event loop.

JavaScript is a single-threaded language that has many use-cases for pausing execution on it's main and lone thread while it completes other tasks, yet it is unable to spawn a new thread. The following pseudo-code would disable all interactivity e.g. button clicks, mouse & keyboard events on a web page for a second at a time.

console.log("Foo")
sleep(1)

console.log("Bar")
sleep(1)

console.log("FooBar")
sleep(1)

stop

The problem is JavaScript does near enough everything on a site, so blocking it's only thread is troublesome.

Callbacks

A common work-around for this requirement from the day of old is to use WebAPI functions to hand off work to external implementations.

setTimeout(() => {
  console.log("Foo")

  setTimeout(() => {
    console.log("Bar")

    setTimeout(() => {
      console.log("FooBar")
    }, 1000)
  }, 1000)
}, 1000)

How these calls to WebAPIs work outside of JavaScript's single thread involves the Event Loop, the system most important to a JavaScript engine.

  1. JavaScript call stack is empty, nothing left to execute
  2. Check Microtask queue for resolved/rejected Promise callbacks
  3. Check Macrotask queue for completed timers, I/O operations etc.
  4. If there, return the callback reference to the call stack to be executed

More depth can be reached on the Event Loop over on MDN.

Promise

A Promise can be simplified to a JavaScript object with three states, said object will have two callbacks, one for the resolved value and the other for the rejected value.

const foo = somePromise()

console.log(foo)
Promise { pending }

waiting

When we await a Promise, we are blocking the current scope's execution until said Promise either resolves or rejects. The call stack can still be populated elsewhere.

async function foobar() {
  const foo = await sleep(1)
  console.log("bar")
}

foobar()
console.log("foo")
foo
bar

Now if I needed a value returned from the asynchronous function, I would need to wait for the Promise it returns to resolve or reject. Since we don't care about what data it creates/uses in this example we can just call it and move to the next item on the call stack.

History