does async/await nesting have performance consequences? - javascript

lets consider we have N async functions like:
async function1(){
return await fetch(...);
}
.
.
.
async functionN(){
return await fetch(...);
}
then we have a function like:
async wrapper(){
let var = await fetch(...);
function1();
.
.
.
functionN();
}
would this create one big microtask queue that would effectively block ui thread going to next task before all called functions resolved their awaits?

There is nothing in the microtask queue until promises resolve. Until then other tasks and (UI) events can be processed.
This is because the await operator will make the corresponding async function return immediately, allowing other JS code to execute. In your case the promise is returned by fetch, which in practice will not resolve immediately. So there is nothing blocking here.
Then when the HTTP response makes the fetch promise resolve, a microtask will indeed be created, which, when when executed will restore the corresponding async function's execution context. Your example function has nothing else to do, so that is quickly done.
Note that it does not matter whether this function was originally called from within some other function: at this stage, only that particular function's execution context (in which an awaited promise resolved) is restored without any pre-existing callstack. So it does not return again to the wrapping function. That already happened in the first phase and will not happen again.
Then again there is free event processing until the next fetch promise resolves. And so it continues.

Related

JS: what is async/await benefit when calling api?

how can async/await be used when calling APIs where the data you're fetching is dependent on the rest of the function content under it?
What if the fake API calls here pushed more names to the array? The updateNames function would be called already before the api calls would be done. Wouldn't I want to block the runtime in this case and make it synchronous?
let names = [];
async foo() {
await Promise.all[
this.callAPI1();
this.callAPI2();
]
this.updateNames(names);
}
In your example, this.updateNames will be executed after your API calls are resolved because you have the await keyword and it is the same as doing:
Promise.all([call1(), call2()]).then(() => { this.updateNames() })
If you have any doubt you can always try by simulating an API call with a promisified setTimeout and some console.log:
const simulateAsync = () => {
return new Promise(resolve => {
setTimeout(() => {
console.log("resolved");
resolve();
}, 100);
});
};
async function test() {
await Promise.all([simulateAsync(), simulateAsync()]);
console.log("after");
}
test();
What if the fake API calls here pushed more names to the array? The updateNames function would be called already before the api calls would be done.
updateNames would not be called until both of the callAPI# functions completed; the await makes the code block (yielding control back to the event loop) until both promises (explicitly created or implicit when the functions are async themselves) complete.
If the functions in question don't return promises, executing synchronously/eagerly, then the await Promise.all([...]) is pointless (there were no promises involved), but there's still no risk of updateNames being called before the callAPI# calls finish; either:
callAPI# returns a promise (in which case await blocks the current task until they complete, returning control to the event loop while it waits), or
callAPI# does not return a promise and all the work is done immediately
and in either case, you can't get to updateNames before they finish.
Wouldn't I want to block the runtime in this case and make it synchronous?
You are blocking the current function call/task with the await. Other tasks scheduled on the event loop can run while the await is still blocked though, and that's the advantage to async processing. Every apparently asynchronous thing in JavaScript is sharing that event loop, and if you completely blocked (rather than awaiting an async call), none of them could run. This means, for example:
setTimeout/setInterval calls don't fire
UI drawing operations don't occur
User input is blocked
All events and observer handling code is deferred
Truly synchronous calls would make all of that (and more) wait until the entirety of foo completed. This is why async code is sometimes referred to as "cooperative multitasking"; control is never wrested away from the current task without its consent like you'd see in multithreaded code, instead control is handed back to the event loop voluntarily (via await) whenever a task is blocked waiting on asynchronous processing, or when the task completes. When the async task has a result, it waits for the event loop to hand control back to it, then continues where it left off.

What happens when await suspends an async function?

main(){
PrintLotsOfStuff();
GoShopping();
HaveAGoodDay();
}
PrintLotsOfStuff(){
printDailyNewsDigest();
printWinningLotteryNumbers();
printWeatherForecast();
printBaseballScore();
}
async printDailyNewsDigest() {
var newsDigest = await gatherNewsReports();
print (newsDigest);
}
gathernewsReports() {}
if we look on https://dart.dev/tutorials/language/futures, we can see that gatherNewsReport() and print(newsDigest) get run after all the functions in the function that called the async function.
However, in the situation I outlined above, there is one more level. In that case, how does the flow look?
First PrintLotsOfStuff() calls printDailyNewsDigest(), which calls gatherNewsReports(), which then suspends, passing control back to printLotsOfStuff().
This then runs printWinningLotteryNumbers, printWeatherForecast, and printBaseballScore. What happens next if the await still hasn't returned?
Does it return to the upper level and then run GoShopping() and HaveAGoodDay()?
First PrintLotsOfStuff() calls printDailyNewsDigest(), which calls gatherNewsReports, which then suspends, passing control back to printLotsOfStuff().
Exactly. In other words: printDailyNewsDigest() executes synchronously till it reaches the first await, then the function yields its execution and the function call evaluates to a Promise (so a Promise gets returned to the function that called it). As PrintLotsOfStuff() ignores that promise, execution will continue synchronously from then on.
This then runs printWinningLotteryNumbers, printWeatherForecast, and printBaseballScore. What happens next if the await still hasn't returned?
Synchronous execution can't be interrupted. printDailyDiggest definetly did not continue execution yet.
Does it return to the upper level and then run GoShopping() and HaveAGoodDay()?
Sure.
Now if that was done, the call stack is empty and the engine has time to execute the next task. Now somewhen whatever printDailyDiggest awaited will be done, and printDailyDiggest will continue its execution

using forEach and async/await, behaves different for node and Jest

I have a function that writes data to a mongodb, like so:
const writeToDB = async (db, data) => {
const dataKeys = Object.keys(data)
dataKeys.forEach(async key => db.collection(key).insertMany(data[key]))
}
This works fine if I run it in a node script. But when I tried to use it in Jest's beforeAll I got this async error from Jest:
Jest did not exit one second after the test run has completed. This
usually means that there are asynchronous operations that weren't
stopped in your tests.
after some troubleshooting I found out that forEach was causing the trouble. Using a for loop solved this problem:
const writeToDB = async (db, data) => {
const dataKeys = Object.keys(data)
for (const key of dataKeys) {
await db.collection(key).insertMany(data[key])
}
}
Searching for this problem I came across this article:
https://codeburst.io/javascript-async-await-with-foreach-b6ba62bbf404
The explanation there made sense, but it left me with some questions:
According to the article, it should not have worked even in the node
script. How come it did?
Why is executing it in node different from running it in Jest?
edit
After reading all the comments, I realise my first question was a bit nonsense. Normally I assign the result of an async function to a variable, and if I don't put await, there will an undefined error down the line. But that's not the case here, so script exits normally and the db writes happen concurrently in the background.
The existing answer already explains in detail why forEach shouldn't be used with promises the way it's used. forEach callback doesn't take returned promises into account and breaks promise chain. async..await needs to be used with for..of to evaluate promises in series or with Promise.all and map to evaluate in parallel.
Jest supports promises and expects that a promise that is returned from asynchronous function (it, etc) signifies that asynchronous process that occurred in this function has ended.
Once Jest finishes a test, it checks if there are open handles that prevent Node from exiting. Since promises weren't returned and chained by Jest, processes that they represent prevent Jest from finishing test process.
This problem is represented by said error message:
Jest did not exit one second after the test run has completed.
This usually means that there are asynchronous operations that weren't
stopped in your tests. Consider running Jest with --detectOpenHandles
to troubleshoot this issue.
Async functions work even in contexts that don't await or call .then() on them, that is, I can definitely do this:
async function foo() {
// async calls here
}
foo(); // no await or .then().
This means you cannot wait for the operation to finish, you can't use the value, and worst of all, you cannot catch or recover from any async errors that might be thrown (or rejected, if we're being accurate)
The main difference is that the .forEach() doesn't care or wait for the operations to finish before calling the next one (since async functions return immediately), whereas your for..of call uses await to wait for each operation to complete before moving on to the next.
Your first .forEach example would be roughly equivalent to the bottom one if you removed the await from the call inside of the loop.
The result is that your first example returns a Promise that resolves immediately, rather than after all of your DB calls were finished, so the test expects the operations to finish, but they are not. In the second example, you properly await for all the calls to finish before the async function finishes, so the return Promise will only resolve after all of the calls resolve themselves, first.
On that note, the two examples are not equivalent because the first would call the insertMany one after the other without waiting for them to finish, causing the DB calls to perform in parallel.
If you want to preserve this behavior, but still return a correct Promise which waits for everything to finish, you should be using [].map() instead of [].forEach:
const writeToDB = async (db, data) => {
const dataKeys = Object.keys(data)
const allPromises = dataKeys.map(async key =>
await db.collection(key).insertMany(data[key])) // run all calls in parallel
return await Promise.all(allPromises); // wait for them all to finish
}

Resolve await when message arrives

I have some websocket code in JS. I have a message-handling loop like this:
socket.addEventListener('message', function (event) {
payload = JSON.parse(event.data)
method = payload.method
// Dispatch messages
if (method == 'cmd1') {
handle_cmd1(payload); // trigger event/semaphore here to wait up waiter
}
else if (method == 'cmd2') { ... }
});
And elsewhere, I have a button callback like this:
$('#my-button').change(function() {
handle_button();
});
async function handle_button() {
send_msg('msg1', 'hi');
// wait for server to reply with cmd1
cmd1_data = await something(); // what?
alert(`cmd1 data: $(cmd1_data)`);
}
The idea is that the button sends 'msg1' and the server is supposed to reply with 'cmd1' and some info. I want to wait for that reply and then do some more stuff.
So my question is how to interlock these? In C++ I'd use a semaphore. I'd rather not spin-loop; is there something in Javascript/JQuery I can use to trigger and then wait for a user-defined event like this? I'm sort of new to JS, and very new to JS async/await.
EDIT: I've made a simple jsfiddle to show what I'm after.
http://jsfiddle.net/xpvt214o/484700/
Now that I understand how promises in Javascript work, here's a working example of a promise that can get woken up from anywhere by calling a function:
wakeup = null;
// returns a promise that will be resolved by calling wakeup()
// (could be a list of these or whatever, this is just a simple demo)
function wakeable() {
return new Promise( (resolve) => {
wakeup = () => { resolve(true); }
});
}
// demo of waiting and getting woken up:
async function handle_event() {
while (true) {
console.log("waiting...")
await wakeable(); // returns to event loop here
console.log("handle event woke up!");
}
}
handle_event(); // start in "background"
wakeup(); // wake it up
setTimeout(_ => { wakeup(); }, 500); // wake it up again after a delay
What's happening here is that when you call wakeable(), it returns a promise. That promise is constructed with an anonymous function (the one taking resolve as arg); the promise constructor synchronously calls that function, passing it the promise's resolve method. In our case, the function sets wakeup to another anonymous function that calls the original resolve; it's a closure so it has access to that resolve function even when it's called later. Then it returns the new promise.
In this demo, we then await on that promise; that puts the pending promise on a queue, saves the current function state, and returns, like a generator calling yield.
A promise is resolved when its resolve function is called; in this case calling wakeup() calls the promise's internal resolve() method, which triggers any .then methods on the next tick of the Javascript event loop (using the promise queue mentioned above). Here we use await, but .then(...) would work the same way'
So there's no magic; I/O and timeout promises work the same way. They keep a private registry of functions to call when the I/O event or timeout happens, and those functions call the promise's resolve() which triggers the .then() or satisfies the await.
By the way, unlike async in python, leaving a pending promise "open" when the process exits is perfectly fine in Javascript, and in fact this demo does that. It exits when there's no more code to run; the fact that the while loop is still "awaiting" doesn't keep the process running, because it's really just some closures stored in a queue. The event loop is empty, so the process exits (assuming it's in node.js -- in a browser it just goes back to waiting for events).
something() should be a method that returns a promise() or should be another method that is also notated with async.
function something(){
return new Promise(resolve,reject) {
//... call your database
// then
resolve(theResult)
// or
reject(theError)
}
}
The async and await for the most part are really just wrappers around promises. The await returns when the promise calls resolve, and throws an exception when the promise calls reject.
Your async function can return another promise; If it returns another value, it gets turned into a resolved promise with that value.

What is meant by 'pause' when async function await expression 'pauses' execution?

I am very new to the concept of asyncronicity in Javascript and I wanted to make sure I am not misinterpreting what I am reading.
Consider this pseudo code in some Angular app:
async ngOnInit() {
this.responseObject = await this.myService.myGetRequest();
//do some more stuff down here
}
My understanding is that the ngOnInit() will 'pause' or stop execution at await, and NOT execute the code below that line until the promise object(data) is returned? Correct?
await used within an async function awaits the fulfillment of a Promise value or converts the variable to a Promise.
Yes, you are correct the code at next line will not be executed until the previous line which uses await has returned a fulfilled Promise or the value is converted to a Promise.
Note, it is not clear what the pattern
await this.responseObject = await this.myService.myGetRequest();
is expected to achieve. The first await should be able to be omitted
this.responseObject = await this.myService.myGetRequest();
The async function declaration will return a Promise that is resolved with the returned valued from your function call.
If you add the await expression, it will stop the async execution and wait until your promise is resolve to continue executing the other instructions in your code, somehow making it behave like a 'synchronous' function.
Do some reading over here: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function
I feel the two above answers don't answer the core question. What does javascript mean when it says it's awaiting something? Javascript is singly threaded. So, instead of dealing with async itself, it runs all tasks in a separate task queue provided by the "runtime" (usually either the browser or node). When we await a piece of code, we are placing the promise on the task queue, waiting on the promise to finish, and then taking it off the task queue fo code execution to finish. For example, consider the following code block.
setTimeout(() => console.log("Finished Outer Timeout"), 0);
async function asyncCall() {
console.log("Before Await")
function resolveAfter2Seconds() {
return new Promise(resolve => {
setTimeout(() => {
console.log("Finished Inner Timeout")
resolve('resolved');
}, 2000);
});
}
await resolveAfter2Seconds()
console.log(`After Await`);
}
asyncCall()
If await fully blocked the task queue, we would expect the logging order to be:
Before Await
After Await
Finished Outer Timeout
Finished Inner Timeout
as the timeouts would have to wait on the task queue for the main script to finish. However, the ordering is:
Before Await
Finished Outer Timeout
Finished Inner Timeout
After Await
which indicates that the task queue can still run the two timeout functions even when the main script is awaiting. Note there may be differences depending on your browser, but this is generally how Javascript async/await works.
Work Cited:
https://jakearchibald.com/2015/tasks-microtasks-queues-and-schedules/
https://medium.com/#sadia.islam.badhon/callback-queue-vs-microtask-queue-9dc6a790330e
https://html.spec.whatwg.org/multipage/webappapis.html#queuing-tasks

Categories