8

I'm searching for some eslint option, or some other way to detect missing the 'await' keyword before calling async methods inside a class. Consider the following code:

const externalService = require('./external.service');

class TestClass {

constructor() { }

async method1() {
    if (!await externalService.someMethod()) {
        await this.method2();
    }
}

async method2() {
    await externalService.someOtherMethod();
}

module.exports = TestClass;

There will be no warning if I will convert method1 to:

async method1() {
    if (!await externalService.someMethod()) {
        this.method2();
    }
}

I tried to do on the '.eslintrc' file:

"require-await": 1,
"no-return-await": 1,

But with no luck. Anyone have an idea if it is even possible? Thanks a lot!

Or Assayag
  • 5,662
  • 13
  • 57
  • 93
  • Calling an `async` function without `await` is actually totally valid, you just get a `Promise` in return so I doubt linters allow this kind of rule (which is very restrictive) – Guerric P Apr 20 '21 at 08:24
  • @GuerricP I edited the code, method2 will call external method (let's say you don't know what's the code in there, it's a third party code). – Or Assayag Apr 20 '21 at 08:28
  • 1
    You might be able to use rule [no-floating-promises](https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/no-floating-promises.md). It behaves slightly different, but I think it will catch this case. – Automatico Apr 20 '21 at 09:00
  • @Automatico, Thanks, will check it. – Or Assayag Apr 20 '21 at 09:03
  • 1
    This is a very good question and in my view a very important thing to check for. Missing awaits can cause really nasty asynchronous/race condition bugs. In my view, despite what others here have said, it is rarely intended to not await an async and in those cases where it is the author should be explicitly disabling a lint rule and making clear why they're doing it. – Nathan Dolan Jan 04 '23 at 18:33

4 Answers4

7

typescript-eslint has a rule for this: no-floating-promises

This rule forbids usage of Promise-like values in statements without handling their errors appropriately ... Valid ways of handling a Promise-valued statement include awaiting, returning, and either calling .then() with two arguments or .catch() with one argument.

As you probably figured out from the name, typescript-eslint is designed to add TypeScript support to eslint, but you can use it with JavaScript as well. I guess it's your call to decide whether it's overkill for this one rule, but here are the steps:

  1. Generate a tsconfig.json file

    npx tsc --init
    
  2. Install dependencies

    npm install --save-dev eslint @typescript-eslint/eslint-plugin @typescript-eslint/parser
    
  3. Modify your .eslintrc file

    Based on my testing it looks like you'll need these entries at a minimum:

    {
      "parser": "@typescript-eslint/parser",
      "parserOptions": { "project": "./tsconfig.json" },
      "plugins": ["@typescript-eslint"],
      "rules": {
        "@typescript-eslint/no-floating-promises": ["error"]
      }
    }
    

If there are places where you want to call an async function without using await, you can either:

  • Use the void operator as mentioned in the documentation for the rule, e.g.

    void someAsyncFunction();
    
  • Or just change error to warn in the .eslintrc configuration above

The documentation for setting up typescript-eslint is here for more info: https://typescript-eslint.io/docs/linting/linting

Next time you run eslint you should see the rule applied:

$ npm run lint
...
./services/jobService.js
  11:5  warning  Promises must be awaited, end with a call to .catch, end with a call to .then with a rejection handler or be explicitly marked as ignored with the `void` operator  @typescript-eslint/no-floating-promises

Since you mentioned VS Code specifically, this also integrates great with the ESLint plugin:

vscode showing no-floating-promises rule

bmaupin
  • 14,427
  • 5
  • 89
  • 94
  • this should be the accepted answer – Josep Alsina Jan 02 '22 at 17:41
  • I found to use void to pass the check, I had to add an option to the ESLint rule `"@typescript-eslint/no-floating-promises": ["error", { "ignoreVoid": true }],` – PulpDood Jul 20 '23 at 06:57
  • @PulpDood It's odd you had to set `ignoreVoid` to `true` since that's already the default as per [the documentation](https://typescript-eslint.io/rules/no-floating-promises/#options) – bmaupin Jul 22 '23 at 17:06
3

require-await says "Don't make a function async unless you use await inside it".

This is because async has two effects:

  • It forces the function to return a promise
  • It lets you use await inside it

The former is rarely useful which mean if you aren't using await inside the function you need to question why you marked it as async.


no-return-await stops you from doing:

return await something

Because await unwraps a value from a promise, but returning a value from an async function wraps it in a promise.

Since just returning a promise causes that promise to be adopted, combining return with await is just bloat.


So neither of those do what you want.

Which brings us to your actual desire.

Such a feature does not (as far as I know) exist in ESLint, and I don't think it would be useful to have one.

There are lots of use cases where you don't want to await something returned by an async function.

e.g.

const array_of_promises = array_of_values.map( value => do_something_async(value) );
const array_of_resolved_values = await Promise.all(array_of_promises);

The above is a common use-case where you want to run a bunch of async functions in parallel and then wait for them all to resolve.

Another example is the case that no-return-await is designed to detect!

Cases like these are common enough that most people wouldn't want their toolchain calling them out for doing it.

Quentin
  • 914,110
  • 126
  • 1,211
  • 1,335
1

There is an ESLint plugin for this that you can use as an alternative to the typescript-eslint route:

https://github.com/SebastienGllmt/eslint-plugin-no-floating-promise

Magnus
  • 598
  • 1
  • 5
  • 9
1

Consider using the combination of eslint rules for a better version of checking await:

No-floating-promises

return-await with try-catch option to able catch the Promise rejection (reference)

nmDat
  • 749
  • 1
  • 9
  • 18