In async functions in JavaScript, returning the result from an async function (i.e. a Promise object) is the same as returning the fulfilled value from that Promise. In code, assuming innerFn is async, this:

async function outerFn() {
  return await innerFn();
}

is the same as this:

async function outerFn() {
  return innerFn();
}

But this goes even further. A "thenable" is just any JS object that has a then() function. All Promises are thenables. But returning a thenable from an async function, even if it's not a Promise, will run that function automatically for you, and there doesn't seem to be a way to turn off the magic.

So, for example:

function innerFn() {
  const a = {};
  a.then = onFulfilled => onFulfilled(123);
  return a;
}

await outerFn(); // returns `123`

It's even encouraged to let this magic run (i.e. to skip the await from outerFn, in this example), even though it cripples stack traces and arguably obscures what's happening.

Now, consider the following example:

function innerFn() {
  return {
    id: 1,
    name: 'John Smith',
    then: () => 'What an unfortunate name for a function in this object. It would be a shame if everything broke because of it.',
  };
}

async function outerFn() {
  return innerFn();
}

const result = await outerFn();
console.log(result); // Never happens :(
Final thoughts

We don't like magic or surprises here. And we do like complete stack traces. My god.

Previous on JavaScript
Mastodon Mastodon