1

I have an async function calling some asynchronous code:

async function foo(test){
  const value = await call(test)
}

I then call this function from another file without:

   foo(5)

The linter is throwing a warning saying Missing await for an async function call. This does not make sense to me.

The asynchronous code inside the function is called with await keyword which makes foo run sequentially when called right?

Why is this warning being thrown?

bcsta
  • 1,963
  • 3
  • 22
  • 61
  • 1
    `foo` can be called without the await keyword, but to use the result of the promise, you'll need to use `await` or `.then` – evolutionxbox May 11 '22 at 15:25
  • 1
    It depends on whether you need to wait for `foo()` to finish or not in the caller. – Barmar May 11 '22 at 15:27
  • 1
    *"which makes foo run sequentially"*: how else could a JS function run? What do you mean with sequentially? As opposed to what? – trincot May 11 '22 at 15:27
  • what result of the promise? `value` ? – bcsta May 11 '22 at 15:27
  • This is a linter warning, not a JavaScript error. Usually async functions should be awaited, but it depends on the use case. – Barmar May 11 '22 at 15:28
  • I should have phrased it better. What I meant is that once the await call finishes inside `foo`, then there is no code being executed asynchronously anymore correct? – bcsta May 11 '22 at 15:28
  • @bcsta `async` is syntactic sugar that makes a function return a Promise that resolves with whatever the function's return value is, or reject with whatever error is thrown. Similarly, `await` is syntactic sugar that acts like `.then()` without needing the function chaining usually associated with `then`. Whether you use async/await or have a function that returns a "real" promise and code that `.then().catch()`es the result is basically the same thing, using different syntax. The linter error isn't great though, it's really just "be careful: your code is getting a Promise, not a value!" – Mike 'Pomax' Kamermans May 11 '22 at 15:29

2 Answers2

3

Should javascript async function be called with await keyword?

That depends. If you want the logic where the code is calling foo() to wait until foo has completed its asynchronous work, then yes, you should use await.

If you don't want the logic at the call site to wait for foo to finish its work, don't use await, but do store the promise it provides and await (or otherwise use it) at some point. Otherwise, you won't handle it if the promise is rejected, and not handling promise rejections is a source of bugs.

So either (in an async function):

await foo();

or (in a non-async function):

return foo(); // Let the caller handle the promise

or (in a non-async function where you can't return the promise to your caller):

foo()
.then(value => {
    // ...use value (except your `foo` doesn't return one,
    // so really this is just "foo worked")...
})
.catch(error => {
    // ...handle/report error...
});

etc.

Fundamentally, though: An async function returns a promise, so you should do something to handle that promise (by passing it back to your caller, or handling it locally, etc.).

Let's look at what happens in an async function calling foo when call fails, both with and without await:

With await:

async function call(test) {
    if (test === undefined) {
        throw new Error("'test' is required");
    }
    // Stand-in for something asynchrojous using `test`
    const result = await new Promise(resolve => {
        setTimeout(() => {
            resolve(test * 2);
        }, 10);
    });
    return result;
}

async function foo(test) {
    const value = await call(test)
    console.log(`foo got value: ${value}`);
}

async function example() {
    foo();
}

example()
.then(() => {
    console.log("All done, no errors");
})
.catch((error) => {
    console.error("Error: " + error);
});

When you run that, it says "All done, no errors" but if you look in the devtools console, you'll see "Uncaught (in promise) Error: 'test' is required" (an unhandled rejection).

Compare with using await:

async function call(test) {
    if (test === undefined) {
        throw new Error("'test' is required");
    }
    // Stand-in for something asynchrojous using `test`
    const result = await new Promise(resolve => {
        setTimeout(() => {
            resolve(test * 2);
        }, 10);
    });
    return result;
}

async function foo(test) {
    const value = await call(test)
    console.log(`foo got value: ${value}`);
}

async function example() {
    await foo();
}

example()
.then(() => {
    console.log("All done, no errors");
})
.catch((error) => {
    console.error(error);
});

As you can see, the error from call was caught (it propagated from call to foo, then from foo to example, then from example to where we call it and handle errors).

T.J. Crowder
  • 1,031,962
  • 187
  • 1,923
  • 1,875
  • It seems like it might be declared `async` just so it can use `await` internally, and the promise that returns is irrelevant. – Barmar May 11 '22 at 15:31
  • @Barmar - The promise tells us when it finishes, and separately tells us whether it failed, even when you don't return a value. – T.J. Crowder May 11 '22 at 15:31
  • Consider the case of an async IIFE, which is just used so you can use `await` within its body. – Barmar May 11 '22 at 15:33
  • This means that there will be a chain of async-await functions all the way to the top of the execution stack? – bcsta May 11 '22 at 15:35
  • And also shouldn't the code inside `foo` stop to wait for `await call()` finishes? If thats the case then when `foo` finally finishes executing there should not be any asynchronous code left right? i.e. promise is always handled by the end of `foo` – bcsta May 11 '22 at 15:38
  • @Barmar - That's fine **if** A) You don't care when it finishes, and B) You have `try`/`catch` around the whole thing. But that's not the common case. – T.J. Crowder May 11 '22 at 15:39
  • @bcsta - There's always a chain. It may not be `async` functions (it may be functions using promises explicitly), but there's always a chain (sometimes multiple chains). It's just a matter of where and how you handle them. – T.J. Crowder May 11 '22 at 15:40
  • @bcsta - *"And also shouldn't the code inside foo stop to wait for await call() finishes?"* When you call `foo`, here's what happens: It creates a promise, calls `call`, resolves the promise it created to the promise it got from `call`, and then returns the promise it created. Note that the work `call` does **is not done yet** (in the normal case). The promise `foo` returns tells you when that's done (and whether it succeeded). (Re the term: "resolve to" -- I wrote up promise terminology [here](https://thenewtoys.dev/blog/2021/02/08/lets-talk-about-how-to-talk-about-promises/).) – T.J. Crowder May 11 '22 at 15:41
  • I know how it works. My point is that you may not care when it completes. It wasn't declared `async` so you could `await` it, just so you could `await call()` – Barmar May 11 '22 at 15:42
  • @Barmar - Okay. :-) My point is that fire-and-forget async calls are rare. In the normal case, and what we should be teaching people, you need to use the promise that you get back -- either to wait for the result, handle errors, or propagate errors to the caller. *Not* using `await` fails to handle errors and is one of the most common errors I see in code using `async` functions. – T.J. Crowder May 11 '22 at 15:46
  • Indeed, that rarity is why eslint has this warning on by default – Barmar May 11 '22 at 15:47
-2

When you declare a function async it will always return a Promise. When a function returns a Promise you should treat it as if it is asynchronous, and you should figure out if any function that calls this function marked with async should wait for that function to complete, or explicitly allow it to run asynchronously if it needs to.

You either need to await your call to foo (which would make whatever function you are calling it in also an async function), or use .then(..) to execute your code after your call to foo succesfully completes. Alternatively if you want the code to execute asynchronously you can explicitly declare this using the syntax of your linter right above the call to foo(5).

Sumurai8
  • 20,333
  • 11
  • 66
  • 100
  • 2
    The keyword `async` doesn't make a function run asynchronously. – jabaa May 11 '22 at 15:31
  • The `async` keyword will always return a `Promise`, regardless if it contains any `await` s. As far as I know it is entirely up to the browser if it executes the content of that function immediately or schedules it. – Sumurai8 May 11 '22 at 15:36
  • 1
    That's wrong. https://jsfiddle.net/g7L85d1x/ Every browser that correctly implements the specification will print `1`, `2`, `3` in this order. – jabaa May 11 '22 at 15:38
  • Add a `const x = ` before the call to function `f(..)`, log it and observe that it indeed returns a Promise. Just because your browser currently executes it synchronously does not mean it will always do that. If a function is marked `async` you should always treat it as if it is asynchronous. – Sumurai8 May 11 '22 at 15:41
  • 1
    _"Just because your browser currently executes it synchronously does not mean it will always do that"_ But it's written in the specs. A browser has to synchronously run the code. Otherwise, it couldn't return a promise. A function has to synchronously run until it returns. Async functions always return a promise. That's correct. But the rest of the first paragraph in your answer is wrong. _"If a function is marked `async` you should always treat it as if it is asynchronous."_ No, you shouldn't. You should understand the code flow of your code. – jabaa May 11 '22 at 15:41
  • After reading some specs I stand corrected. https://tc39.es/ecma262/multipage/control-abstraction-objects.html#sec-newpromisecapability After copying the execution context it will execute that context before doing anything else, so a function without awaits will be guaranteed to execute before statements after it. I stand by my advice to treat functions with `async` as if they are asynchronous, even if they are not. If you do not remove `async` from a synchronous function and instead squelch linter warnings you introduce hours of pain for future you or a future coworker. – Sumurai8 May 11 '22 at 19:46
  • If a function is marked `async` you should be positive that whatever code uses that function is written in the mindset that that function might be asynchronous, not with the mindset that it currently isn't. If that is not the case, refactoring that function to be actually asynchronous requires future you or a future coworker to check the invocation of that function everywhere, even though the function definition didn't change. The code should have behaved the same, but now it does not and instead causes race conditions. – Sumurai8 May 11 '22 at 19:49
  • 1
    Some developers understand the code flow of their code and other developers try to guess the code flow. Obviously, you prefer to be in the second group. Your answer is still wrong and you should avoid to misinform other people. JavaScript is a single-threaded deterministic language. You can always forecast the behavior as long as there are no external factors. – jabaa May 11 '22 at 19:55
  • Some people don't have the time to read and understand every single function in an entire application if they need to make changes, especially if three other deadlines are lined up. Setting up code in such a way that future developers, either me or someone else, are less likely to trip over something that is avoidable is part of my work. ¯\\_(ツ)_/¯ – Sumurai8 May 11 '22 at 20:03
  • 1
    Now I understand, why so many systems get hacked when developers have so many deadlines and don't even know language basics. – jabaa May 11 '22 at 20:06