Why should we wrap await inside an async function? - javascript

Why should we have an async function in order to use await? Why can't we just use await without async? JS is async by default too, this just adds to the confusion.
Update:
I've seen some lads put my question on hold so I'll try to elaborate.
I'm just curious as to why this won't work:
some code
let users = await getUsers();
some code
Why should it be inside an async for it to work, ie
$(async function() {
some code
let users = await getUsers();
some code
});

JS is async by default too...
No, JavaScript is not async by default. The only async features of JavaScript are fairly newly-added:
Promise resolution
async/await
JavaScript is commonly used in environments where you interact with asynchronous things (like event handlers in the DOM, or I/O completions in Node.js), but JavaScript is not asynchronous (other than above).
In the words of Allen Wirfs-Brock, who was the editor of the ECMAScript specification for many years, JavaScript...
(has) an observably synchronous execution model. Other than via Atomics/SABs there are no observable shared-state race conditions.
Back to your question:
Why should we have an async function in order to use await?
Before too long, with modules you won't have to, once the top level await proposal finishes working through the process. It just got to Stage 3.
But the answer is that await is syntactic sugar for consuming a promise, and one of the rules of promises is that you either handle errors or return the chain to the caller (so it can handle errors or return the chain to its caller). await doesn't handle errors, so it has to return the chain to the caller. The way it does that is that an async function always returns a promise, and that promise is chained to the promise await awaits.
That is, this:
async function foo() {
const thingy = await somethingAsyncReturningAPromise();
return thingy.foo;
}
is conceptually (but not literally) this:
function foo() {
return somethingAsyncReturningAPromise()
.then(thingy => thingy.foo);
}
If something goes wrong in somethingAsyncReturningAPromise, the promise returned by foo rejects — the error is propagated to the caller.
As far as I can tell from the top-level await proposal, it simply allows unhandled rejections at the top level of the module to be unhandled rejections. So just like this code causes an unhandled error:
null.doSomething();
this code in an async module would cause an unhandled rejection:
await somethingThatReturnsAPromiseAndRejects();

Why should we have an async function in order to use await? Why can't we just use await without async?
Because async/await is "just" syntactic sugar for Promises. If the function is async, then it returns a Promise. It is not possible to have the "await" behaviour without returning a promise. The fact that the function is async has to be marked explicitly.
JS is async by default too, this just adds to the confusion.
This statement is too "simplified". While it is true that JS is async in nature, because of the event loop, this doesn't mean that every function has an async behavior. This does not add to the confusion. You're probably confused due to misunderstanding how JS really works. You should read about Promises, which are behind the scenes when you see async/await.

JavaScript has task based concurrency. It basically means that code blocks (tasks) runs synchronously without being interrupted, until it reaches a certain breakpoint (the end of a task). That has some advantages:
1) You do have concurrency, e.g. a network call does not block your script from doing other things in the meantime (so the task that consumes the network request will only run if the network request was done, other tasks can be done in the meantime).
2) On the other hand, you do not have concurrent mutations, which eliminates a lot of problems (let a = 1; a += 1; could evaluate to 3, you would need locks / semaphores to prevent those, c.f. Java & others).
Now async / await allow you to define such tasks:
An async function can be divided into tasks, await serves as a breakpoint:
let a = 1;
async function stuff() {
a = a + 1; // this is totally secure, as no other code might run in the meantime
a = a + await other(); // this is unsafe, as we await, which means that other tasks might be executed in the meantime.
}
If you want to "await in non async functions" that basically means that you won't know wether a certain function call runs synchronously (meaning: without other code running in the meantime) or not:
function dangerous() { await stuff(); }
let a = 1;
a = a + dangerous(); // does that work or not? Can a be manipulated in the meantime?
So with your proposal you would basically remove the borders around tasks, every code might run every inbetween. So at the end that causes chaos, and chaos is not good if you want to be productive.

Related

Promise.allSettled rewrite in async await

I got a piece of code that would submit several set of reports which they are independent each other, currently wrote in promise.allSettled, but I was told that the team standard would require async await instead of promise
"Never use multiple await for two or more independent async parallel tasks, because you will not be able to handle errors correctly. Always use Promise.all() for this use case."
1
"In comparison, the Promise returned by Promise.all() may be more appropriate if the tasks are dependent on each other / if you'd like to immediately reject upon any of them rejecting."
2
"Using for await...of, you have more granular control of the promises. So if the order in which promises complete is important to you, for await...of is your preferred choice. However, the increased control isn’t free. The fact that for await...of handles promises one by one, makes it a lot slower."
"To sum up, the three methods are all capable of handling iterables of promises, but differ slightly in their functioning. Use for await of if the order in which promises are resolved is important to you. Use Promise.all() if the order isn’t important and you need all calls to succeed. Use Promise.allSettled() if the order isn’t important and you don’t absolutely need all individual calls to be successful."
3
After some research, I found it is not possible to rewrite it in async await with the same efficiency (request execute in parallel) and simplicity (promise.allSettled is a built-in function), am I correct?
That piece of code
const recordInsertErrors:Object[] = [];
await Promise.allSettled(
jsonArray.map((eachPositionReport) => {
return PositionReport.query().insert(eachPositionReport).catch((err) => {
const error = { vessel_ownership_id: eachPositionReport.vessel_ownership_id, error: err.nativeError };
recordInsertErrors.push(error);
throw err;
});
}),
);
First of all JavaScript code does not run in parallel. The most we can say is that it executes asynchronously, i.e. it gets executed by the engine while monitoring its job queues. The "only" thing that might execute in parallel is lower-level, non-JavaScript logic, such as provided by some APIs that make asynchronous HTTP requests.
Secondly, whether an asynchronous operation starts while another is still underway, is not determined by the use of Promise.all, Promise.allSettled, for await ... of, ...etc, but by whether or not all involved promises are created immediately or not. That is part of the code that is not orchestrated by any of the mentioned constructs.
So surely you can use async and await keywords to achieve that asynchronous requests are made without waiting that a previous one has completed.
For instance:
const recordInsertErrors = [];
const promises = jsonArray.map(async (eachPositionReport) => {
let value;
try {
value = await PositionReport.query().insert(eachPositionReport);
} catch(err) {
value = {
vessel_ownership_id: eachPositionReport.vessel_ownership_id,
error: err.nativeError
};
}
return value;
});
// All promises will now fulfill, as errors are converted to
// fulfillments with an error property
(async () => {
for (const promise of promises) {
const value = await promise;
if (value.error) recordInsertErrors.push(value);
console.log(value);
}
})();
The for loop with await expressions will not delay the moment at which all promises have resolved. It will potentially report sooner on some results than Promise.allSettled, as the latter is designed to first wait until all promises have settled, and only then resolve its own promise.

Why doesn't .then() need the async keyword when used (similar to await)? How does Javascript know it's an asynchronous operation?

I'm starting to learn asynchronous Javascript and I'm really confused.
To be honest, the async/await approach seems very logical to me. We need to let the runtime know we're doing an asynchronous operation so it can handle it accordingly. But why don't we need to do the same when using the .then() method? I mean, if Javascript was already able to understand when promises are handled, couldn't await just be used without async just like .then()?
To make it even more confusing, I saw people using .then() directly inside functions declared with the async keyword. Wasn't async/await supposed to be syntactic sugar for the .then().catch() approach? Why can these be combined, especially inside one another? Using .then() on the result of the async function wouldn't have been as confusing, but being inside one another makes me have even a harder time understanding this.
I really searched everywhere for an explanation on this and couldn't find an answer for this exact question. All I've found was people saying you can use both approaches because they essentially are the same thing, but when you get in the details, things aren't very clear.
So the async function always returns a promise. Inside it, await always handles promises. .then() can be chained to the await function. .then() can also be chained to the result of the async function. Same with the .catch method if we don't want to use try/catch on the await. Why is it so mixed up? Can we handle the return of async without .then()? If async/await really is syntactic sugar for .then(), why doesn't .then() also always return a promise after it resolves?
If anybody can help with some clarification, I would truly appreciate it. Thank you!
There's so many questions here, but one way to think about it is... Promises are not asynchronous operations, they are just a standard pattern that helps users deal with existing asynchronous operations in a predicable way.
There's nothing special about .then(). It's not a Javascript keyword, and javascript doesn't need to be 'aware' of then in any way.
Promises are a pattern. You can write a promise class yourself from scratch (I'd highly recommend this). When you use .then(), and you pass it a function, you are telling the promise class: "When the promise resolves, please call this function for me".
All of this existed before async/await. Async/await was added after to make it easier to work with promises.
I find the question if 'something is syntactic sugar' meaningless. It suggests that a language feature is not that meaningful because the same thing could be accomplished before the language feature existed. While this might be true, this is true for any programming language when compared to, say, assembly. To me the definition of 'syntax sugar' can be extended to almost anything, so it's not a useful designation.
What async/await (and generators before it) adds to the language, is that a javascript function can be interrupted and resumed later after some condition.
Lastly, .then() and .catch() always return promises. If you see a .then() function that doesn't, then it's not compatible with the Promise/A+ spec.
If you are willing to put in the work to fully understand this, I would recommend the following 2 exercises:
Write your own Promise class
Re-implement async/await using generator functions (see the co package of how this was done before async/await landed)
The purpose of async/await is to allow writing async code in a serial manner, which is mentally simpler to reason about (for some human-beings). This is useful, if you need to wait for async operation to finish before you continue with the rest of the code. For example, if you need to pass result of async operation as parameter.
Example 1
function asyncOperation1(n) { return Promise.resolve(n+1); }
function asyncOperation2(n) { return Promise.resolve(n/2); }
function asyncOperation3(n) { return Promise.resolve(n*3); }
function errorHandler(err) { console.error(err); }
function main() {
// flow-control
asyncOperation1(1)
.then(asyncOperation2)
.then(asyncOperation3)
.then(continueAfterAsync)
.catch(errorHandler)
// function wrapper
function continueAfterAsync(result) {
console.log(result);
}
}
main();
With async/await the code of the main function above may look like
async main() {
try {
console.log(
await asyncOperation3(
await asyncOperation2(
await asyncOperation1(1)
)
)
);
} catch(err) {
errorHandler(err);
}
}
Pay attention that we don't need to rewrite async operation functions to be async function asyncOperation... to use await, but we need to declare main function as async main.
Which one is better(?) is the mater of developers's taste and previous programming languages experience. The benefit that I can see is that you don't need to wrap everything into functions and introduce additional flow-control code, leaving this complexity to JavaScript compiler.
However, there are cases, when you want to schedule some parallel tasks and you don't care which one will finish first. These kind of things would be relatively hard to do with async/await only.
Example 2
function main() {
Promise
.all(
['srv1', 'srv2', 'srv3'].map(
srv => fetch(`${srv}.test.com/status`)
)
])
.then(
responses => responses.some(res => res.status !== 200) ?
console.error('some servers have problems') :
console.log('everything is fine')
)
.catch(err => console.error('some servers are not reachable', err))
}
So, we see that there is a room for both .then() and await to coexist.
In some cases function may be either synchronous or asynchronous, depending on business logic (I know it's ugly, but in some cases it's unavoidable). And here we come to your main question
why don't we need to mark an asynchronous operation with .then() and we have to do it with await
In other words, why do we need async keyword at all?
Example 3
// without `async`
function checkStatus(srv) {
if (!srv.startsWith('srv')) {
throw new Error('An argument passed to checkStatus should start with "srv"')
}
return fetch(`https://${srv}.test.com/status`);
}
function main() {
// this code will print message
checkStatus('srv1')
.then(res => console.log(`Status is ${res.status === 200 ? 'ok': 'error'}`))
.catch(err => console.error(err));
// this code will fail with
// Uncaught TypeError: (intermediate value).then is not a function
checkStatus('svr1')
.then(res => console.log(`Status is ${res.status === 200 ? 'ok': 'error'}`))
.catch(err => console.error(err));
}
However, if we define async function checkStatus, compiler will wrap the runtime error into rejected promise return value, and both parts of the main function will work.
Now let's imagine that JavaScript allows to write functions that use await without specifying async in front of them.
Example 4 (not a valid Javascript)
function checkStatus(srv) {
if (cache[srv]) {
data = cache[srv];
} else {
data = (await fetch(`https://${srv}.test.com/status`)).json();
}
data.x.y = 'y';
return data;
}
What would you expect checkStatus to return? Promise, raw value or throw exception (in case data.x is undefined)?
If you say Promise, then it would be hard for developer that uses this function to understand why inside of checkStatus one can write data.x and outside of it (await data).x is required.
If raw value, the whole execution flow becomes cumbersome, and you can no longer rely on the fact that JavaScript is a single-threaded language, where no-one can change the value of the variable between two lines of code that are written in serial manner.
As you noticed, async/await is a syntactic sugar. If this syntax allows me to avoid possible runtime errors at earlier stage and keep the language backward compatible, I'm eager to pay the price of putting extra async in front of async functions.
Also, I would recommend to read the answers to JS async/await - why does await need async?
In a simple explanation, async/await is syntactic sugar (the node interpreter/compiler/optimizer will convert everything to normal Promises).
The goal of this feature is turn our life easy, because the callback way/style of programming eventually lead us to make mistakes. We call this "the callback hell"
So, we can use .then() when calling functions that is decorated with async keyword, and we can await on functions that return Promise objects.
It's important for optimizations in general, that the code we write tells the compiler what we are meaning, in terms of performance. Imagine if all our codes / lines of code / instructions could be async or sync at the same time. This would lead the computers perform bad, because the task of check this during runtime is very expensive.
So that's why it's so important to us code instructions in an efficient manner.

Clarification regarding promises and async/await needed

I have been using Promises and async/await, they are pretty much the same thing right? Usually what I would do is wrap my promise and return it etc.
function someFetchThatTakesTime(){
// Promisify the request.
return new Promise((resolve, reject) => {
if(allGood){
resolve();
}else{
reject();
});
}
Then I can do:
someFetchThatTakesTime()
.then(console.log('all good.')
.catch(console.log('some error occured.');
or I can do the:
async function wrapMyFetch() {
try {
// Make the async call.
data = await someFetchThatTakesTime();
return data;
} catch(err) {
// Propagate the exception up stream.
throw err;
}
}
(async () => {
let response = await wrapMyFetch();
// Do stuff with the response.
})();
I guess, pretty clear so far.
However, I have recently encountered situations where my application does not care about waiting for the results to be fetched and the data containers to be updated etc. Let's say there is one larger loop that runs infinitely and any Promised requests simply will fill-in the gaps as the application runs.
In that case, we don't really need the async/await pattern right? We just want to move forward with our loop and stones will fall in place behind us, whenever they are ready to fall in place (or not, in case of an error).
I would like to clarify this: async/await will just force things to run linearly, right? But if we do not want linearity, and we are ok with spawned tasks - Promises - to finish their thing in the background in some time when we are resuming with our run-cycle, we don't need that async/await pattern? Is that correct?
I have been using Promises and async/await, they are pretty much the same thing right?
Well, async/await are helpful syntax built on top of promises. Async/await relies on promises in order to work. So, I wouldn't quite call them the same thing, but you can code identically functioning code with await surrounded by try/catch and .then().catch(). You can use either. Both methods rely on promises.
However, I have recently encountered situations where my application does not care about waiting for the results to be fetched and the data containers to be updated etc. Let's say there is one larger loop that runs infinitely and any Promised requests simply will fill-in the gaps as the application runs.
In that case, we don't really need the async/await pattern right? We just want to move forward with our loop and stones will fall in place behind us, whenever they are ready to fall in place (or not, in case of an error).
There is no rule that the caller has to pay attention to a returned promise or has to wait for it before going on with their task. So, if you want to "just let the stones fall as they may in the background" as you say and that's appropriate for your application, you can code that way.
This is often referred to as "fire and forget" coding where you start some asynchronous operation that is going to do something on its own and you don't need to pay attention to when it finishes or if it had an error because that is of no consequence to the calling code.
But, there is a rule that you can't let a promise rejection go unhandled. So, if you're returning a promise and the caller isn't going to do anything with it, then you have to make sure that any rejections are handled by the operation itself (with .catch()) so that the only thing that ever gets returned back is a resolved promise. Even if all you do is notice the error and do nothing with it, you have to catch the error.
async/await will just force things to run linearly, right?
Not globally, no. await makes the code in the async function it's used in wait for the promise you pass it to settle, but that only affects the function you use it in, not anything calling that function.
async functions are syntactic sugar for writing a function that returns a promise. await is syntactic sugar for consuming promises. Together, they dramatically simplify using promises (in particular by making rejections errors that automatically propagate through the call tree).
More specifically, an async function runs its code synchronously until the first await or return (or until code runs off the end of the function), at which point it returns a promise. Its logic then waits for the promise to settle, and continues; eventually it settles its promise based on what happened to the last promise it awaited or returned (or fulfills it with undefined if code execution runs off the end).
If you don't want to wait for a promise to settle, you don't have to, not even in an async function. Just don't use await on it. For instance, assume we have this wrapper for fetch that gets JSON for us (and fixes the API footgun):
async function fetchJSON(...args) {
const response = await fetch(...args);
if (!response.ok) {
throw new Error("HTTP error " + response.status);
}
return response.json();
}
Here's an async function using it that does an initial query to get a list of things to fetch, and then fetches all of those things in parallel::
async function fetchList(listUrl) {
const list = await fetchJSON(listUrl);
return Promise.all(list.map(item => fetchJSON(itemUrl)));
}
Notice how fetchList uses await to wait for the list of things to fetch, but then doesn't wait for the items; it just returns the promise from Promise.all for the list of items it starts fetching.
Also note how await within fetchJSON makes fetchJSON's logic wait for the fetch promise to settle, but doesn't make the caller calling fetchJSON wait unless that caller uses await. fetchList only awaits the first call (to get the list), it doesn't wait for the item from the list.

Use await outside async

Is there a way to make the javascript await keyword work outside async functions? I would like to be able to freeze the entire call-stack (instead of just the rest of the async function), to be resumed once the particular promise returns a value. Sadly a powerful await like that is currently gimped or not implemented yet. I tried to make nodent.js work, but due to my custom loader and dynamic functions it's unfortunately impractical.
Is there a way to make the javascript await keyword work outside async functions?
Sadly, the answer is: No.
See the docs:
The await expression causes async function execution to pause, to wait for the Promise's resolution, and to resume the async function execution when the value is resolved. [emphasis added]
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/await
I would like to be able to freeze the entire call-stack (instead of just the rest of the async function), to be resumed once the particular promise returns a value.
But how could your promise return a value if the entire call-stack was frozen? It would be stuck forever.
More details
To use await you need to be inside async function.
At least you have to do:
async function main() {
// use await here
}
main();
in your main code.
Or, using an IIFE - thanks to Robert Klep for the suggestion:
void async function main() {
// use await here
}();
Or, if you like punctuation:
(async () => {
// use await here
})();
Other options
There are some other options to handle concurrency in Node, like:
https://www.npmjs.com/package/co
https://www.npmjs.com/package/bluebird-co
https://www.npmjs.com/package/async-co
http://bluebirdjs.com/docs/api/promise.coroutine.html
http://taskjs.org/
none of which will freeze the call stack, however, as it would have the same effect as running kill -STOP with your own process ID and waiting for the stopped process to resume itself.
Given you are looking for a hack, not a proper promise-based concurrency solution, have a look at node-fibers (there are similar ones, but afaik this is the most popular, with multiple abstractions built around it). It does allow you to halt the current fiber in any synchronous function until something asynchronous happens that runs in a different fiber. Which of course is a horrible idea, but well…

Synchronous exceptions in async functions

What happens if an async function throws an exception synchronously?
Example:
async function whatHappen () {
throw new BombError()
// no "await" here
}
try {
whatHappen().catch(() => console.error('rejected'))
} catch (e) {
console.error('thrown')
}
I've run this example in Babel and it seems that the throw is automatically caught and translated into a rejected promise, so the example will log "rejected" to the console.
But does this reflect the actual specification and how it will be implemented in JavaScript engines? I've tried reading the technical proposal but the specification is obviously not aimed at language users but language implementers.
Can I rely on async functions to always return a promise or are there situations where they might throw an exception synchronously? Is there any scenario where calling an async function without await should be wrapped in a try/catch block?
Yes, an async function always returns a Promise.
In the technical proposal that you linked to, there is a sentence in the beginning stating that:
A similar proposal was made with Deferred Functions during ES6 discussions. The proposal here supports the same use cases, using similar or the same syntax, but directly building upon control flow structures parallel to those of generators, and using promises for the return type, instead of defining custom mechanisms.
(Emphasis mine)
So there is no case where you need to wrap an async function in a try/catch block, as it cannot throw a synchronous error.
PS: I just saw that Chrome Canary and MS Edge have implemented async/await behind a flag, so you could test it there as well.

Categories