11

I am using async/await in several places in my code.

For example, if I have this function:

async function func(x) {
    ...
    return y;
}

Then I always call it as follows:

async function func2(x) {
    let y = await func(x);
    ...
}

I have noticed that in some cases, I can omit the await and the program will still run correctly, so I cannot quite figure out when I must use await and when I can drop it.

I have concluded that it is "legitimate" to drop the await only directly within a return statement.

For example:

async function func2(x) {
    ...
    return func(x); // instead of return await func(x);
}

Is this conclusion correct, or else, what am I missing here?

EDIT:

A small (but important) notion that has not been mentioned in any of the answers below, which I have just encountered and realized:

It is NOT "legitimate" to drop the await within a return statement, if the called function may throw an exception, and that statement is therefore executed inside a try block.

For example, removing the await in the code below is "dangerous":

async function func1() {
    try {
        return await func2();
    }
    catch (error) {
        return something_else;
    }
}

The reason is that the try block completes without an exception, and the Promise object returns "normally". In any function which calls the outer function, however, when this Promise object is "executed", the actual error will occur and an exception will be thrown. This exception will be handled successfully in the outer function only if await is used. Otherwise, that responsibility goes up, where an additional try/catch clause will be required.

goodvibration
  • 5,980
  • 4
  • 28
  • 61
  • Example when you have big data and dont want to lose information you need to use the await, because general operation close the connection without the information get complete – HudsonPH Nov 21 '17 at 08:48
  • Another example is using api, you need to wait the server response so you need to use await to dont lose information – HudsonPH Nov 21 '17 at 08:49

4 Answers4

8

If func is an async function then calling it with and without await has different effects.

async function func(x) {
  return x;
}

let y = await func(1); // 1
let z = func(1) // Promise (resolves to 1)

It is always legitimate to omit the await keyword, but means you will have to handle the promises in the traditional style instead (defeating the point of async/await in the first place).

func(1).then(z => /* use z here */)

If your return statements use await then you can be sure that if it throws an error it can be caught inside your function, rather than by the code that calls it.

Dan Prince
  • 29,491
  • 13
  • 89
  • 120
  • So is my empiric conclusion correct or not? If yes, the only reason to do `return await` is "safety", for example, if at some point in the future someone modifies the function and stores the result in some temporary variable (perhaps in order to do few more things with it) before returning? Is that correct? – goodvibration Nov 21 '17 at 08:54
  • Yes, your conclusion was correct. It is always possible to omit the `await` in the return statement because either way the function will return a promise and it is the responsibility of the caller to handle that correctly. – Dan Prince Nov 21 '17 at 08:58
  • Oh, and since the caller (some other function in my code) always uses `await`, I am "safe"... Thank you!!!!! – goodvibration Nov 21 '17 at 09:00
  • Exactly, although it's worth a look at this question for the finer details about handling errors in the return statement: https://stackoverflow.com/questions/38708550/difference-between-return-await-promise-and-return-promise – Dan Prince Nov 21 '17 at 09:01
  • I got it. Another way to explain why my code works without the `await` is by noting that with `func2` doing `return await func()`, any function which calls `await func2()` will essentially be doing `await await func()`, which means that there is a redundant `await`. – goodvibration Nov 21 '17 at 09:06
  • Remember that async functions __always__ return a promise, regardless of whether you await in the return statement. Your calling code is always going to be set up to handle a promise (either with await or `.then`) so a closer analogy would be `func().then().then()` which isn't necessarily redundant. – Dan Prince Nov 21 '17 at 09:18
2

await just lets you to treat promises as values, when used inside an async function.

On the other hand, async works quite the opposite, it tags the function to return a promise, even if it happens to return a real, synchronous value (which sounds quite strange for an async function... but happens often when you have a function that either return a value or a promise based on conditions).

So:

  • I have concluded that it is "legitimate" to drop the await only directly within a return statement.

In the last return statement of an async function, you just are returning a Promise, either you are return actually a directly a promise, a real value, or a Promise-as-value with the await keyword.

So, is pretty redundant to use await in the return statement: you're using await to cast the promise to a value -in the context of that async execution-, but then the async tag of the function will treat that value as a promise.

So yes, is always safe to drop await in the last return statement.

PS: actually, await expects any thenable, i.e. an object that has a then property: it doesn't need a fully spec compliant Promise to work, afaik.

PS2: of course, you can always drop await keyword when invoking synchronous functions: it isn't needed at all.

Sergeon
  • 6,638
  • 2
  • 23
  • 43
  • "you're using `await` to cast the promise to a value -in the context of that async execution-, but then the `async` tag of the function will treat that value as a promise." - awesome explanation!!! – goodvibration Nov 21 '17 at 09:54
  • There is no such thing as returning an synchronous value from an async function. If you `return x` from an async function it is the same as calling `Promise.resolve(x)`. Once you define a function as async, it by defintion returns a promise – k0pernikus Nov 21 '17 at 13:03
  • For reference: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function – k0pernikus Nov 21 '17 at 13:05
1

An async function always returns a Promise.

So please keep in mind that these writing of an async function are all the same:

// tedious, sometimes necessary
async function foo() {
    return new Promise((resolve) => resolve(1)))
}

// shorter
async function foo() {
    return Promise.resolve(1)
}

// very concise but calling `foo` still returns a promise
async function foo() {
    return 1 // yes this is still a promise
}

You call all of them via foo().then(console.log) to print 1. Or you could call them from another async function via await foo(), yet it is not always necessary to await the promise right away.

As pointed out by other answers, await resolves the promise to the actual return value statement on success (or will throw an exception on fail), whereas without await you get back only a pending promise instance that either might succeed or fail in the future.

Another use case of omitting (i.e.: being careful about its usage) await is that you might most likely want to parallelize tasks when writing async code. await can hinder you here.

Compare these two examples within the scope of an async function:

async function func() {
    const foo = await tediousLongProcess("foo") // wait until promise is resolved
    const bar = await tediousLongProcess("bar") // wait until promise is resolved

    return Promise.resolve([foo, bar]) // Now the Promise of `func` is marked as a success. Keep in mind that `Promise.resolve` is not necessary, `return [foo, bar]` suffices. And also keep in mind that an async function *always* returns a Promise.
}

with:

async function func() {
     promises = [tediousLongProcess("foo"), tediousLongProcess("bar")]
     return Promise.all(promises) // returns a promise on success you have its values in order
}

The first will take significantly longer than the last one, as each await as the name implies will stop the execution until you resolve the first promise, then the next one.

In the second example, the Promise.all the promises will be pending at the same time and resolve whatever order, the result will then be ordered once all the promises have been resolved.

(The Bluebird promise library also provides a nice Bluebird.map function where you can define the concurrency as Promise.all might cripple your system.)

I only use await when want to work on the actual values. If I want just a promise, there is no need to await its values, and in some cases it may actually harm your code's performance.

k0pernikus
  • 60,309
  • 67
  • 216
  • 347
  • @goodvibration I extended my answers with a more generic example showcasing that aync function always return a promise and provided further links to external sources better explaining promises. – k0pernikus Nov 21 '17 at 13:28
  • 1
    @PhilippClaßen Good catch. – k0pernikus Apr 08 '19 at 14:04
-1

I got a good answer above, here is just another explanation which has occurred to me.

Suppose I have this:

async function func(x) {
    ...
    return y;
}

async function func2(x) {
    ...
    return await func(x);
}

async function func3(x) {
    let y = await func2(x);
    ...
}

The reason why I can safely remove the await in the return statement on func2, is that I already have an await when I call func2 in func3.

So essentially, in func3 above I have something like await await func(x).

Of course, there is no harm in that, so it's probably better to keep the await in order to ensure desired operation.

goodvibration
  • 5,980
  • 4
  • 28
  • 61
  • It's all wanting to work on promises or their success values. It's not necessarily better to keep the await especially if you are working on multiple promises within the same async function. If you chain a lot of await calls it might hinder your performance drastically when compare to resolving them through e.g. `Promise.all`. See my answer for details. – k0pernikus Nov 21 '17 at 13:11
  • So performance-wise, `await` on return statement is always a minus? – goodvibration Nov 21 '17 at 13:21
  • Not always. Depends on what your caller is interessted in. If your caller awaits your promise, then no, the chain is quick. If your caller wants to aggregate the promises and resolve them at the same time, then yes. This is why you should be careful about how to chain and when to resolve promises. Have a look at my example in my answer. Please have a look at my answer. It's more imortant that you understand the concept of promises. – k0pernikus Nov 21 '17 at 13:24
  • @k0pernikus: OK, I'll put it this way: In an answer here by Sergeon, he describes it pretty neatly (assuming it's correct) - `async function func` will always convert the returned value into a `Promise` object. So although `return await ...` will convert the returned value into a "regular" object, it will be converted back into a `Promise` object before returned. Hence no point in doing `return await`. My conclusion is therefore that `await` is redundant, but can be used harmlessly, just so that If I change my code in the future, it will be there to ensure correctness. Where is my mistake? – goodvibration Nov 21 '17 at 13:51
  • As I have said, it might be harmless in the beginning. Yet have a look at my example with the collection of promises. If you await each and every one, it takes much longer than using `Promise.all`. If you get use to the `await foo();await bar() ` approach you'll do yourself a disservice. Await is not redundant, it changes the type on the right side from a promise to an actual value (Even if that value is then directly wrapped inside a new promise). I recommend only using `await` when you want to work on the values of a promise. – k0pernikus Nov 21 '17 at 14:04
  • @k0pernikus: I'm sorry, but I find the statement "Even if that value is then directly wrapped inside a new promise" hard to understand. What I see in `return await func()` is a conversion of the promise returned from `func` into an actual value, and then back into a promise. What's the point in that??? Unless you wanna keep it for "safety", for example (which I have also described in a comment to one of the answers here) - if at some point in the future someone modifies the function and stores the result in some temporary variable in order to do few more things with it before returning. – goodvibration Nov 21 '17 at 14:17
  • When somebody modifies the function with a temporary variable they should know if they want to work on the promise or the actual success value. Only in the latter they should await the promise to do further computation. Yet it might also make sense to just aggregate a bunch of promises and use Promise.all; no await necessary. await is a tool that has its purpose. Your "safety" feature is an illusion. If you want to know if you are working on a promise or a value, using TypeScript will be more of an help. – k0pernikus Nov 21 '17 at 14:26
  • @k0pernikus: So to conclude what you're saying, `return` should always be preferred over `return await`, which is necessarily unnecessary. Is that correct? – goodvibration Nov 21 '17 at 14:30
  • Pretty much. `await` make sense within the body of an async function. Yet if you want to return the Promise provided by another async function, there is no need to `return await asyncFunc()` but it's more straight-forward to "directly" return that promise via `return asyncFunc()`. – k0pernikus Nov 21 '17 at 15:32
  • @k0pernikus: So then do you agree with everything that I wrote in this answer to my own question, except for the statement at them bottom? – goodvibration Nov 21 '17 at 16:47
  • Yes, your observation is valid, I disagree with the conclusion " it's probably better to keep the await". As I have said in my first comment: "It's not necessarily better to keep the await especially if you are working on multiple promises within the same async function. " – k0pernikus Nov 21 '17 at 17:03
  • @k0pernikus: OK, got it. Thank you very much for the constructive conversation, and for the enlightenments along the way!!! – goodvibration Nov 21 '17 at 17:06