-.- --. .-. --..

Promises: Promise.reject is not the opposite of Promise.resolve

Hi Jake 👋🏽

A quirk I ran into today: Promise.reject, if passed a promise object as an argument won’t wait for that promise the way Promise.resolve does. At first, this seemed like a bug, so I checked out the spec, it was quite clear—that pseudo-pseudo-code is super readable for us mere mortals, isn’t it? ISN’T IT?—that the behaviour between these two functions is different.

Background

This post assumes you’re familiar with the fetch API. If not, here’s some refresher:

  1. The call to fetch returns a promise with two possible paths: a response (success) and an error. Both objects that get passed to these respective callbacks have similar API (interface)
  2. Both the success and error objects have functions defined to parse the body into text, or json, or binary blobs. The json() function returns a promise that resolves to the parsed JSON output, or an error if the parsing fails.

Problem

The problem can be better explained with the comparison between the two following code snippets:

fetch()
  .then(response => Promise.resolve(response.json())
  .then(value => {
    // “value” here is the parsed output.
   });
   
fetch()
  .then(response => Promise.reject(response.json())
  .then(_, value => {
    // “value” here is a promise, that wraps the
    // response from json()
    })

Now, from what I understand, this is in line with the spec, where there is a difference between the definition of Promise.reject and Promise.resolve.

I’m not entirely sure about why this design decision exists. My initial guess was that the semantics become a bit weird because if you called Promise.reject(x.json()), and x.json() resolved, should that value be force-rejected?

Anyhow, this force-reject functionality can be achieved by an explicit function:

const rejectResolved = (promise) => {
  return promise.then(success => Promise.reject(success));
};

If the error block is not provided, the error value flows down to the next chain.

And use this in place of Promise.reject in the earlier case:

fetch()
  .then(response => rejectResolved(response.json())
  .then(_, value => {
    // “value” here is now the
    // response from json(), either a parse error, or 
    // the parsed JSON object
    })