eslint: Rule Change: return await is both faster and allows better error handling

What rule do you want to change?

no-return-await

What change to do you want to make?

Generate fewer warnings

How do you think the change should be implemented?

A new default behavior

Example code

async function do(url) {
  return await fetch(url);
}

What does the rule currently do for this code?

Eslint will say to remove await with the docs saying:

What will the rule do after it’s changed?

Suggest return await. (Or at least not misinform authors).

Participation

  • I am willing to submit a pull request to implement this change.

Additional comments

The docs state:

You can avoid the extra microtask by not awaiting the return value, with the trade off of the function no longer being a part of the stack trace if an error is thrown asynchronously from the Promise being returned. This can make debugging more difficult.

This is actually false and if that is the reasoning then it should be changed. I have a long explanation of the mechanics as to why return await is faster at this Stackoverflow comment but to keep this brief, you can run this:

async function test(name, fn) {
  let tick = 0;
  const tock = () => tick++;
  Promise.resolve().then(tock).then(tock).then(tock);

  const p = await fn();
  console.assert(p === 42);
  console.log(name, tick);
}

await Promise.all([
  test('nonpromise-sync', () => 42),
  test('nonpromise-async', async () => 42),
  test('nonpromise-async-await', async () => await 42),
  test('promise-sync', () => Promise.resolve(42)),
  test('promise-async', async () => Promise.resolve(42)),
  test('promise-async-await', async () => await Promise.resolve(42)),
]);

setTimeout(() => {}, 100);

And you’ll see that returning a Promise (not using return await) is the slowest method.

There is also an added benefit (maybe) that execution (resolving) happens within the function itself, so you can catch errors. If you try to add a catch to a returning Promise, it’ll never fire.

function errorThrower () {
  return Promise.reject('fail');
}
function returnNoAwait() {
 try {
     return errorThrower();
 } catch {
   console.log('I want to catch, but I am never executed');
 }
} 
async function returnAwait() {
  try {
      return await errorThrower();
  } catch(e) {
    console.log('I caught an error. I still have e, so I can rethrow.');
  }
}
await returnAwait();
await returnNoAwait();

Yields:

I caught an error. I still have e, so I can rethrow. Uncaught fail

And note that uncaught fail will crash some environments.

About this issue

  • Original URL
  • State: closed
  • Created a year ago
  • Comments: 15 (15 by maintainers)

Commits related to this issue

Most upvoted comments

It seems like there’s agreement to update the documentation for this rule. @clshortfuse do you want to submit a PR for that?

I think such an update would make the rule classify as stylistic?

I think yes, since it reports logical redundancy that no longer negatively impacts performance according to the current state of the ES spec and implementations.

Hi @clshortfuse, thanks for the issue!

There have been a lot of discussions about this rule.

You can avoid the extra microtask by not awaiting the return value, with the trade off of the function no longer being a part of the stack trace if an error is thrown asynchronously from the Promise being returned. This can make debugging more difficult.

This is actually false and if that is the reasoning then it should be changed. I have a long explanation of the mechanics as to why return await is faster at this Stackoverflow comment but to keep this brief, you can run this

I think this was true at the time when the rule was created but may have become false after https://github.com/tc39/ecma262/pull/1250.

What will the rule do after it’s changed?

Suggest return await. (Or at least not misinform authors).

We can’t change the no-return-await rule to suggest return await. I think the best past forward for this rule is to update the docs saying that the rationale for the rule is to warn about redundant code.