javascriptnode.jspromiseevent-loop

JavaScript Promise handlers schedule behaviour


Given this Promise chain.

function getData() {
  return new Promise((resolve) => {
    // ...
  })
    .then((data) => data.someData)
    .then((rawData) => processData(rawData))
    .catch((err) => console.log(err));
}

I've got two questions:

  1. then() schedules a callback to run when the Promise instance it's attached to sees a change in its state from "pending" to "fulfilled" (and another optional callback that is scheduled to run when status changes from "pending" to "rejected"). But, it's rather vague how this scheduling works. I mean, since then() method (not the callback passed into it) runs synchronously itself, how is this "scheduling" performed? Does the Promise instance have some sort of internal list associated with fulfilled and rejected states and then() push callbacks into these lists and then, when state change happen, the callbacks are pushed into the microtask queue? Same question applies to catch().

  2. since we were talking about catch(), say we have an error somewhere in the then() chain in the snippet above. Since we only pass the first callback function (that handles success) to the then() chains, the error is propagated somehow down the chain to the catch() block and it's handled there. But again, it's not very clear this works. I mean, then() returns a new Promise instance and the way I see it, these Promise instances are independent, in that they have their own separate states and their own separate lists of scheduled callbacks (if this presumption is true), and catch() would normally register an callback for "rejected" state only to the last Promise instance created by then((rawData) => processData(rawData)).


Solution

  • Does the Promise instance have some sort of internal list associated with fulfilled and rejected states and then() push callbacks into these lists and then, when state change happen, the callbacks are pushed into the microtask queue?

    Yes, precisely that.

    Same question applies to catch().

    Same thing there. In fact, catch is just a wrapper around then for convenience, implemented literally as

    // class Promise {
    …
    catch(onRejected) {
      return this.then(undefined, onRejected);
    }
    

    An error is propagated somehow down the chain to the catch() block and it's handled there. But these Promise instances are independent, in that they have their own separate states and their own separate lists of scheduled callbacks,so catch() would normally register an callback for "rejected" state only to the last Promise instance?

    Yes, it would. The solution is that the error propagation doesn't work by magically determining and jumping to the nearest error handler, but rather rejecting one promise after the other in the chain.

    A promise created by .then(onFulfilled, onRejected) gets resolved with the result of the respective handler callback (when that callback is executed), or gets rejected when that callback throws. And if you don't pass any callback for the parameter, as in .then(onFulfilled, undefined), .then(undefined, onRejected) or even .then(undefined, undefined), the then method still registers a reaction for both state changes on its receiver, and by default forwards the result or error onto its result promise.

    So when the first promise in that chain rejects, there's a rejection reaction on it that will reject the second promise, which will in turn schedule its rejection reaction, which will reject the third promise, which will in turn schedule the handler that .catch() registered on it.