44

Consider this code:

async function load() {
  const data = await new Promise(resolve => {
    setTimeout(() => resolve([1, 2, 3]), 10);
  }).then(data => data.map(i => i * 10));
  console.log(`Data inside the function: ${JSON.stringify(data)}`);
  return data;
}

function main() {
  const data = load();
  console.log(`Loaded data: ${JSON.stringify(data)}`);
}

main();

This is the output I'm getting:

Loaded data: {}
Data inside the function: [10,20,30]

But if I change the code to this:

async function load() {
  const data = await new Promise(resolve => {
    setTimeout(() => resolve([1, 2, 3]), 10);
  }).then(data => data.map(i => i * 10));
  console.log(`Data inside the function: ${JSON.stringify(data)}`);
  return data;
}

async function main() {
  const data = await load();
  console.log(`Loaded data: ${JSON.stringify(data)}`);
}

main();

I'll get this:

Data inside the function: [10,20,30]
Loaded data: [10,20,30]

I'm confused because based on the documentation, await should pause the execution till the promise is resolved. In which case, the first example should return data as an array. But as you can see, it's returning a Promise and I have no idea why?!

At the same time, the documentation has this part which I don't understand what it is talking about:

An await can split execution flow, allowing the caller of the await's function to resume execution before the deferred continuation of the await's function. After the await defers the continuation of its function, if this is the first await executed by the function, immediate execution also continues by returning to the function's caller a pending Promise for the completion of the await's function and resuming execution of that caller.

It seems to me the await is working only if all of the functions in your code are async which is ridiculous since if I'm using a function from another module, how should I know if it's an async or not!? Or maybe I should take the cautious side and always call all the functions with an await regardless of whether they are async or not!!!

[UPDATE]

Thanks to everyone participating and providing me with insight. But I'm still confused how should I be using await and async. Should I always call all of my function with an await?

Let's say I'm writing a code composed of multiple functions within multiple files. If I end up using a library which returns a Promise or it's an async function, should I trace back all my function calls from the asynchronous point to the entry point of the application and add an await before all the function calls after making them async? Or maybe I should just get into the habit of calling all my functions with an await regardless of whether they are async or not?

Mike
  • 14,010
  • 29
  • 101
  • 161
Mehran
  • 15,593
  • 27
  • 122
  • 221
  • 1
    Regarding your edit: There is no point using `await` on anything that isn't a Promise. Presumably when you call a function from a library and try to use its return value, you already know whether it returns a Promise or not. The documentation for the library would tell you, and it'd be difficult for you to find out that a function exists at all without finding out whether or not it returns a Promise. – Paul Jul 05 '19 at 02:37
  • When you call a function that returns a Promise you can use await, but you don't have to. You might want to use the Promise object directly too – Paul Jul 05 '19 at 02:41
  • 1
    That part I understand and I accept that it would be on me to know the return value of any function I call. At this point, my problem with the implementation of `async` and `await` is more of a good practice. Do you always assume that the code you will be writing in the future will always return a `Promise`? If not, what will happen if some code which supposed to be synchronous turns out to be asynchronous? You need to go back to the entry point of your application and await all the calls, right? How do you avoid such changes? – Mehran Jul 05 '19 at 02:44
  • You usually know if your code will need to wait on something asynchronous or not before you write it, and if it doesn't need to, it is unlikely it will need to in the future. If you do need to ever change an existing interface from sync to async, you are correct that it does have a sort of cascading avalanche effect on every caller and callers' callers and so-on. That is the case with regular callback functions too. That seems like an unlikely scenario though; I don't believe I have ever had to change an existing interface for something from synchronous to asynchronous. – Paul Jul 05 '19 at 03:33
  • Lucky you :) it's been what I was doing for the past few hours – Mehran Jul 05 '19 at 03:37
  • Sorry to hear that. Maybe there is an IDE that can "auto-asyncify" functions across many files as needed. I don't know, but it seems like something that could be automated pretty well. – Paul Jul 05 '19 at 12:53

3 Answers3

99

All async functions return a promise. All of them.

That promise will eventually resolve with whatever value you return from the async function.

await only blocks execution internal to the async function. It does not block anything outside of the function. Conceptually, an async function starts to execute and as soon as it hits an await instruction, it immediately returns an unfulfilled promise (in the pending state) from the function and the outside execution world gets that promise and continues to execute.

Sometime later, the internal promise that was being awaited will resolve and then the execution of the rest of the internals of the function will continue. Eventually the internals of the function will finish and return a value. That will trigger resolving the promise that was returned from the function with that return value.

FYI, there's a lot of superfluous stuff in your load() function. You can change it from this:

async function load() {
    const data = await new Promise(resolve => {
        setTimeout(() => resolve([1, 2, 3]), 10);
    }).then(data => data.map(i => i * 10));
    console.log(`Data inside the function: ${JSON.stringify(data)}`);
    return data;
}

to this:

function load() {
    return new Promise(resolve => {
        setTimeout(() => resolve([1, 2, 3]), 10);
    }).then(data => data.map(i => i * 10));
}

Then, use it like this:

load().then(result => {
    console.log(result);
});

Or, I prefer to encapsulate the manual creation of promise in their own function like this:

function delay(t, v) {
    return new Promise(resolve => {
        setTimeout(resolve.bind(null, v), t);
    });
}

function load() {
    return delay(10, [1, 2, 3]).then(data => data.map(i => i * 10));
}

And, it turns out this little delay() function is generally useful in lots of places where you want to delay a promise chain.


Thanks to everyone participating and providing me with insight. But I'm still confused how should I be using await and async.

First off, most of the time you only mark a function async if you need to use await inside the function.

Second, you most commonly use await (from within an async function) when you have multiple asynchronous operations and you want to sequence them - often because the first one provides a result that is used as input to the second. You can use await when all you have is a single asynchronous operation, but it doesn't really offer much of an advantage over a simple .then().

Here are a few examples of good reasons to use async/await:

Sequencing multiple asynchronous operations

Imagine you have getFromDatabase(), getTheUrl() and getTheContent() that are all asynchronous. If any fails, you would want to just reject the returned promise with the first error.

Here's how this looks without async/await:

function run() {
    return getFromDatabase(someArg).then(key => {
        return getTheURL(key);
    }).then(url => {
        return getTheContent(url);
    }).then(content => {
        // some final processing
        return finalValue;
    });
}

Here's how this looks with async/await:

async function run(someArg) {
    let key = await getFromDatabase(someArg);
    let url = await getTheURL(key);
    let content = await getTheContent(url);
    // some final processing
    return finalValue;  
}

In both cases, the function returns a promise that resolves with the finalValue so these two implementations are used the same by the caller:

run(someArg).then(finalValue => {
    console.log(finalValue);
}).catch(err => {
    console.log(err);
});

But, you will notice that the async/await implementation has more of a serialized, synchronous look to it and looks more like non-asynchronous code. Many find this easier to write, easier to read and easier to maintain. The more processing you have between steps, including branching, the more advantages accrue to the async/await version.

Automatically catching both rejected promises and synchronous exceptions

As I said earlier, async functions always return a promise. They also have to built-in error handling that automatically propagates errors back to that returned promise.

It goes without saying that if you manually return a promise from the async function and that promise rejects, then the promise returned from the async function will reject.

But also, if you are using await and any promise you are awaiting rejects and you don't have a .catch() on the promise and don't have a try/catch around it, then the promise the function returns will automatically reject. So, back in our previous example of this:

async function run(someArg) {
    let key = await getFromDatabase(someArg);
    let url = await getTheURL(key);
    let content = await getTheContent(url);
    // some final processing
    return finalValue;
}

If any of the three promises that are being awaited rejects, then the function will short circuit (stop executing any more code in the function) and reject the async returned promise. So, you get this form of error handling for free.

Then lastly, an async function also catches synchronous exceptions for you and turns them into a rejected promise.

In a normal function that returns a promise such as we had earlier:

function run() {
    return getFromDatabase(someArg).then(key => {
        return getTheURL(key);
    }).then(url => {
        return getTheContent(url);
    }).then(content => {
        // some final processing
        return finalValue;
    });
}

If getFromDatabase() throws a synchronous exception (perhaps triggered because someArg is invalid), then this overall function run() will throw synchronously. That means that for the caller to catch all possible errors from run(), they have to both surround it with a try/catch to catch the synchronous exceptions and use a .catch() to catch the rejected promise:

try {
    run(someArg).then(finalValue => {
        console.log(finalValue);
    }).catch(err => {
        console.log(err);
    });
} catch(e) {
    console.log(err);
}

This is messy and a bit repetitive. But, when run() is declared async, then it will NEVER throw synchronously because any synchronous exception is automatically converted to a rejected promise so you can be sure you are capturing all possible errors when it's written this way:

async function run(someArg) {
    let key = await getFromDatabase(someArg);
    let url = await getTheURL(key);
    let content = await getTheContent(url);
    // some final processing
    return finalValue;
}

// will catch all possible errors from run()
run(someArg).then(finalValue => {
    console.log(finalValue);
}).catch(err => {
    console.log(err);
});

Should I always call all of my function with an await?

First, you would only ever use await with a function that returns a promise as await offers no usefulness if the function does not return a promise (just adding to the clutter of your code if not needed).

Second, whether you use await or not depends upon the context of both the calling function (since you HAVE to be in an async function to use await and on the flow of logic and whether it benefits from using await or not.

Places where it's pointless to use await

async function getKey(someArg) {
    let key = await getFromDatabase(someArg);
    return key;
}

The await here isn't doing anything useful. You're not sequencing multiple async operations and you're not doing any processing on the return value. You can accomplish the exact same code by just returning the promise directly:

async function getKey(someArg) {
    return getFromDatabase(someArg);
}

And, if you know that getFromDatabase() never throws synchronously, you can even remove the async from the declaration:

function getKey(someArg) {
    return getFromDatabase(someArg);
}

Let's say I'm writing a code composed of multiple functions within multiple files. If I end up using a library which returns a Promise or it's an async function, should I trace back all my function calls from the asynchronous point to the entry point of the application and add an await before all the function calls after making them async?

This is a bit too general of an ask that's hard to answer in a general case. Here are some thoughts along this general direction:

  1. Once any part of your result that you're trying to return from your function A() is asynchronous or uses any asynchronous operation to obtain, the function itself is asynchronous. In plain Javascript, you can never return an asynchronous result synchronously so your function must use an asynchronous method to return the result (promise, callback, event, etc...).

  2. Any function B() that calls your asynchronous function A() that is also trying to return a result based on what it gets from A() is now also asynchronous and also must communicate its result back using an asynchronous mechanism. This is true for a function C() that calls B() and needs to communicate back its result to the caller. So, you can say that asynchronous behavior is infectious. Until you reach some point in the call chain where you no longer need to communicate back a result, everything has to use asynchronous mechanisms to communicate the result, error and completion.

  3. There's no specific need to mark a function async unless you specifically need one of the benefits of an async function such as the ability to use await inside that function or the automatic error handling it provides. You can write functions that returning promises just fine without using async on the function declaration. So, "NO" I don't go back up the call chain making everything async. I only make a function async if there's a specific reason to do so. Usually that reason is that I want to use await inside the function, but there is also the automatic catching of synchronous exceptions that get turned into promise rejections that I described earlier. You would not generally need that with well behaved code, but it is sometimes useful with poorly behaved code orcode with an undefined behavior.

  4. await is also only used when there's a specific reason for it. I don't just automatically use it on every function that returns a promise. I've described above reasons to use it. One can still use .then() just fine for processing the result from a single function call that returns a promise. In some cases, it's just a matter of personal style whether you want to use .then() or await and there is no particular reason it has to be one one way or the other.

Or maybe I should just get into the habit of calling all my functions with an await regardless of whether they are async or not?

Absolutely NOT! First off, the last thing you want to do is to take perfectly synchronous code and unnecessarily make it asynchronous or even make it look asynchronous. asynchronous code (even with async and await) is more complicated to write, debug, understand and maintain than synchronous code so you would never want to unnecessarily make synchronous code into asynchronous code by adding async/await into it:

For example, you would never do this:

async function random(min, max) {
    let r = await Math.random();
    return Math.floor((r * (max - min)) + min);
}

First off, this is a perfectly synchronous operation that can be coded like this:

function random(min, max) {
    let r = Math.random();
    return Math.floor((r * (max - min)) + min);
}

Second off, that first async implementation has made the function a lot hard to use as it now has an asynchronous result:

random(1,10).then(r => {
    console.log(r);
});

Instead of just the simple synchronous use:

console.log(random(1,10));
Mike
  • 14,010
  • 29
  • 101
  • 161
jfriend00
  • 683,504
  • 96
  • 985
  • 979
  • 1
    The whole point of my `load()` function was to do some asynchronous action and make it synchronous using `await` – Mehran Jul 05 '19 at 02:12
  • 1
    @Mehran - You can't do that in Javascript. You can never take an asynchronously obtained value and return it synchronously. Async/await does not do that. Nothing in Javascript does that. You always get a returned promise from an `async` function and no use of `await` inside that function changes that. Please read the details of my answer to understand better how the `async` function works. – jfriend00 Jul 05 '19 at 02:17
  • @jfriend00 You can return an async value synchrounsly. (sync-rpc in npm for example). There is just no reason to ever do it. – Paul Jul 05 '19 at 02:29
  • @Paulpro - Well, that appears to use external code to apparently block the entire Javascript thread which can also be done with the `sync` apis in child_process, but neither of those are really what we're talking about here. – jfriend00 Jul 05 '19 at 02:35
  • It blocks the current process while running the async operation in another one yes. It's how mysql-sync, redis-sync, and several other useless mom modules are built. You can do something similar all in one process too though with a spinlock that uses synchronous I/O to check for an external state change. – Paul Jul 05 '19 at 02:40
  • @Paulpro - I get it. Just not what we're talking about here. We're talking about a value that has an asynchronous Javascript function to obtain. And, as you already know, blocking the whole interpreter should never be done in a server except for perhaps startup code. – jfriend00 Jul 05 '19 at 02:43
  • 2
    @Paulpro But it's still true that you can't do it in javascript - you need to do it in C (you can do it in node.js yes but using node's C APIs instead of node's javascript APIs) – slebetman Jul 05 '19 at 04:04
  • @Mehran - I've added a bunch to my answer related to the updates you added to your question. – jfriend00 Jul 05 '19 at 06:47
  • Thanks. I'm trying my best not to start a discussion here (that's not what SO is for) but at the same time, I disagree with your point that the `async` random function is more complicated than its sync version. I mean if you call it without the `await`, sure. But as soon as you call it with the `await`, it will behave like any ordinary function. And please don't get me wrong, I'm don't like this approach either. But it seems to me that with such a naive implementation of `async` and `await` that JS has, this is the only way to make sure that your code is future proof. – Mehran Jul 05 '19 at 21:40
  • 1
    @Mehran - OK, go ahead and make every single function in your program async if you want. It WILL make things more complicated. If you don't understand that now, then you will understand it better after you try. There's no future proofing needed. A synchronous function won't suddenly become asynchronous in the future without you editing it to be such and if you do, then you modify the design to handle that then. – jfriend00 Jul 05 '19 at 22:01
  • 1
    @Mehran - Plus, just the overhead of creating a promise, returning it, unwinding the event loop just to process the `await` on every single function call to then get the actual return value from the promise will certainly take its toll on performance and garbage collection vs. a plain synchronous function call, not to mention that you're going to now need `await` on every single function call. You seem to have a major gripe about how `async/await` work in Javascript. It is what it is. It's best to learn how to use it as designed or move to a different language that you like better. – jfriend00 Jul 05 '19 at 22:04
  • Can someone give an example of when will moving from ``` async function getKey(someArg) { return getFromDatabase(someArg); } ``` to ``` function getKey(someArg) { return getFromDatabase(someArg); } ``` be problematic. – Ultrablendz Jun 01 '21 at 06:51
  • @Ultrablendz - Those are essentially the same. The only difference is if `getFromDatabase()` throws an exception synchronously (which it shouldn't be doing anyway), then the `async` version will catch the exception and convert it to a rejected promise, and the non-async version will throw synchronously to the caller. – jfriend00 Jun 01 '21 at 20:19
  • Just wanted to thank you for the explanation, honestly, it's excellent! – Lambdaphile Jan 05 '22 at 09:08
6

async/await are just the syntactic sugar, which means, they don't bring any new functionality to the language, being just useful wrappers for promises.

If a function is marked as async, it always returns a promise:

> async function f() { return 42; }
undefined
> f()
Promise { 42 }

Also, if a function is async, you can await for any promise (including the result of another async function) inside it and the execution of the function code will pause at await until that promise is resolved or rejected.

To answer your question: if you use a library function, you normally know if it returns a promise or not (and if it's marked as async, it surely does). So, make sure to await for it or use .then with the returned promise.

afenster
  • 3,468
  • 19
  • 26
  • I don't mean to be rude but how often do you read the code of a library to see if the function you are using is `async` or not? – Mehran Jul 05 '19 at 02:09
  • 1
    I disagree that async/await don't bring new functionality to the language. They allow you to write asynchronous code in much simpler ways than before, particularly when sequencing multiple asynchronous operations. While it's true you can code anything without them, you can also code anything without promises in general, yet promises bring lots of new functionality to the language too. – jfriend00 Jul 05 '19 at 02:13
  • @Mehran - All you need to do is look at the interface of the function and see whether it returns a promise or not. You don't actually care whether than promise is returned automatically because it's tagged as an `async` function or whether the promise is returned manually. Either way, the interface is the same - the caller deals with a returned promise. – jfriend00 Jul 05 '19 at 02:14
  • 2
    I totally agree that `await` and `async` make your code much more readable. What I'm saying is that they are not what they are supposed to be. In my opinion, they are implemented wrong. – Mehran Jul 05 '19 at 02:16
  • 1
    @Mehran - Well, that's just your opinion. They are very useful tools that can make asynchronous coding a lot simpler to code and read and maintain. – jfriend00 Jul 05 '19 at 02:16
  • @jfrield00 - well, I totally agree - `async`/`await` is the next step from promises, just as promises are the next step from regular callbacks, but still, my understanding is that both promises and `async`/`await` are [syntactic sugar](https://en.wikipedia.org/wiki/Syntactic_sugar) - I'm not going to argue about it though :) Surely, they make the code much easier to write. – afenster Jul 05 '19 at 02:25
  • @Mehran you didn't mean to be rude but you actually were rude :) And yes, surely, when I use the library, I do check either its samples or its code to see if the function I'm using returns a promise or not, otherwise, how can I possibly use it at all? – afenster Jul 05 '19 at 02:26
  • 1
    @afenster There is a slight difference. Promises is just a design pattern. Just like any other design patterns - MVC, Singleton, Reactor, Proactor (apparently the design pattern that implements callbacks) etc. But async/await is literally a new language feature. You can use Promises in the original Netscape 4 with a library. You can't do that with async/await (also, Promises is not the only design pattern for handling asynchronous callbacks, there's also Observer (rx.js), Event, Stream etc. but somehow people forget the rest which is a shame because Promise can't do everything) – slebetman Jul 05 '19 at 04:13
  • I'm totally agree with @Mehran: I was still recently using callback functions: the code seemed complicated, but at least there is no ambiguity about its behavior: the documentation (Mozilla site ...) says that the function using await is interrupted, but never says this caller continue ... We tend to believe the opposite ... I just fell into this pittfall. – Didier68 Jun 17 '21 at 15:46
1

Because the first function is asynchronous - so it's running while the rest of the main function executes, which isn't helpful when the result is being logged in the next line. You have to wait for the function to finish executing before you can use the result - so either use async/await as in your example:

async function main() {
  const data = await load();
  console.log(`Loaded data: ${JSON.stringify(data)}`);
}

Or use .then:

function main() {
  load().then(data => {
    console.log(`Loaded data: ${JSON.stringify(data)}`);
  });
}

The hint here is: if the function is async, you have to use it asynchronously, because it always returns a Promise.

Jack Bashford
  • 43,180
  • 11
  • 50
  • 79
  • In Javascript, when the Promise immediately resolves then the callback is run only when call stack is free. What happens in c# when promise immediately resolves? – variable Feb 27 '22 at 17:01