0

This question is theoretical - I have no concrete problem to solve.

With that said, why does the async keyword wrap the return value of an async function in a promise? What's the point? Is it ONLY because the await expression expects a promise? Or is there some meaning / use behind this decision?

VSO
  • 11,546
  • 25
  • 99
  • 187
  • 7
    Because the spec says so. (note that you can `await` non-Promises too, it just doesn't do anything meaningful.) The whole point of using an async function is to be able to `await` things inside it, and anything `await`ed will require the return value to be a `Promise`, so perhaps it was thought to make the return value a `Promise` regardless, even if nothing is `await`ed, for the sake of consistency. – CertainPerformance Jan 01 '19 at 00:04
  • Async/Await uses promises under the hood. There is no way in JavaScript to actually "pause" an executing function to wait for an asynchronous thing to finish. Async/await lets you write code that behaves this way, but it's really syntactic sugar over promises. When this code is actually run, it returns a promise that will resolve with the return value of the function after anything being `await` ed finishes. – shamsup Jan 01 '19 at 00:07
  • 1
    A function has to return synchronously otherwise it would block the thread. What else or any use could it return if not a promise? – Mark Jan 01 '19 at 00:28
  • @MarkMeyer It could just return the value. Or throw if it didn't return a promise, which would imply that the PURPOSE of the async function is to perform an async operation. I am just trying to think through this and explain it away logically to myself and can't. – VSO Jan 01 '19 at 00:33
  • 2
    @VSO if the value is obtained asynchronously, it *can't* be returned because the function will return before the value is available. It the value is obtained synchronously it would be possible just return the value, but then the caller would either need to know the inner workings of function or test the return value each time to see if it was a promise or the value. Seems like constantly returning a promise is a much better idea. Also I think the `async` in front of the function name is enough to imply the purpose of the function is an async operation. – Mark Jan 01 '19 at 00:40
  • 1
    @MarkMeyer Thanks for the help! – VSO Jan 01 '19 at 19:51
  • See [async/await always returns promise](https://stackoverflow.com/questions/43422932/async-await-always-returns-promise) and [async/await implicitly returns promise?](https://stackoverflow.com/questions/35302431/async-await-implicitly-returns-promise) – Bergi Apr 22 '22 at 01:53

2 Answers2

4

I thought i'd answer this primarily because async in Javascript used to confuse the hell out of me, and all of a sudden it snapped, so i hope this analogy may help this happen for you.

You have an async event. This could be anything, getting something from a server, doing something in the browser that takes time, training a machine learning model (!), executing a function or method that uses a setTimeout etc.

The beauty of Javascript and a key reason it works so well for the browser is that it uses the processor thread it runs on in a very clever way that stops the thread from getting blocked by processes that take time (like the ones mentioned above)

Many other languages, for example Ruby run on more than one thread. It is possible to use service workers to run processes on multiple threads in javascript but that is outside the scope of this answer!

The async nature of the JS event loop allows the thread to 'go off' and do something else while it is waiting for a process to finish.

The problem with this from a programming point of view is that it is possible for something in the code that relies on the result of a blocking event to get 'undefined' as a result of the event if it doesn't wait for the event to finish before it tries to use the result of it. Take this piece of code below

let scopedVariable
console.log('the code has started')
setTimeout(() => {
  scopedVariable="I am the result of some async process"
}, 5000);
console.log(scopedVariable)

When the code reaches the console log, the setTimeout hasn't yet completed. As the setTimeout sets the scopedVariable only when it completes, the variable is undefined when we log it

if however

We wrap the timeout in a promise we can await it's resolve callback (first argument of promise) and the code will 'pause' until the promise reaches the resolve callback before continuing.

When we await the promise and the setTimeout completes, the resolve function sets the variable, so that when we console log it it holds the value from the promise

let scopedVariable
const asyncEvent = new Promise ((resolve,fail) => {
  setTimeout(() => {
    resolve(scopedVariable="I have resolved")
  }, 5000);
})
const container = async () => {
  const result = await asyncEvent
  console.log(scopedVariable)
}

container()

You can use await and .then interchangably

For example we could go:

let scopedVariable
const asyncEvent = new Promise ((resolve,fail) => {
  setTimeout(() => {
    resolve(scopedVariable="I have resolved")
  }, 5000);
})
const container = async () => {
  asyncEvent.then(() => console.log(scopedVariable))
}

container()

once again the code will pause at .then and then continue when the asyncEvent promise has resolved.

In fact if we use .then we don't need to enclose it in an async function so we can rewrite it like this

let scopedVariable
const asyncEvent = new Promise ((resolve,fail) => {
  setTimeout(() => {
    resolve(scopedVariable="I have resolved")
  }, 5000);
})

asyncEvent.then(() => console.log(scopedVariable))

The great thing about .then is that the accompanying .catch allows you to catch any errors thrown by the async event (for example if retrieving something from a server when there is an error). For async await you need to wrap potentially dangerous functions in a try catch.

In order to use await you need to be inside an async function (hence the async container function above). This is not necessary with .then, but .then and .catch chains can make your code messy.

I hope this helps!

Community
  • 1
  • 1
Happy Machine
  • 987
  • 8
  • 30
3

The async and await operators are just syntactic sugar that hide the underlying use of promises to implement asynchronous code.

Using async before a function definition makes the function return a promise that resolves to the function's return value, rather than returning normally.

Using await before an asynchronous function call suspends the current function until the promise that it returns is resolved. It's basically equivalent to wrapping the remainder of the function in an anonymous function, and using that as the .then() callback of the promise.

For more information between the relationship, see How to translate Promise code to async await

Barmar
  • 741,623
  • 53
  • 500
  • 612