Working with Clojure, I discovered several issues with how promises interoperate. It made me wonder, what actually is a Javascript promise?

Apparently, there’s a whole website dedicated to explaining what a promise is. From it, we can figure a straightforward definition:

“promise” is an object or function with a then method whose behavior conforms to this specification. In Javascript, Promise.resolve(true) instanceof Promise holds true, but that doesn’t mean it is the only acceptable promise. Studying Clojure’s promesa, I learned that anyone can implement a Promise-like object. After all, the only thing it needs to have is a then method.

So, when writing code that can accept both an immediate value or a promise of one in JS, what do you do? How do you know a promise is a promise?

The answer is: you shouldn’t ever care about that.

If your function receives an immediate that might or might not be a promise, always turn it into a promise with Promise.resolve(), i.e. instead of this:

1
2
3
4
5
6
async function dostuff(val) {
  let v = val;
  if (v instanceof Promise) {
    v = await v;
  }
}

do this:

1
2
3
async function dostuff(val) {
  let v = Promise.resolve(val);
}

Easy right? Well, apparently not.

While Promise.resolve() is relatively cheap for immediates, it still allocates a promise object (obviously). The specification still dictates the creation of the microtask even for a known-resolved promise, thus await Promise.resolve(42) will have to pay the penalty of that, which will be noticeable in the code optimised for performance.

A solution to that would be to see if the value is either a Promise, or a Thenable:

1
2
3
4
5
6
async function dostuff(val) {
  let v = val;
  if ((v instanceof Promise) || (v.then instanceof Function)) {
    v = await v;
  }
}

But, of course, it’s a rather rare occurrence, as the author of prom-client correctly notes.

Maybe we should stop inventing custom promises…