Is leaving useless synchronous code under Async/Await an anti-pattern? - javascript

To my understanding, the point behind await is to 'await' the acting upon the resolved value of a promise until it is encountered as a microtask, as Jake Archibald explains here.
This video by LLJS shows that async-await is essentially syntactical sugar for a generator runner/interpreter function that yields where it awaits and passes the settled value of the promise to the .next() method. This means that the runner's execution of .next() is enqueued as a microtask when an await occurs.
Effectively speaking, all the code under that await will only be executed at the next microtask checkpoint. This can be an issue if code that doesn't require the awaited value of the promise lies underneath it, which is exactly the issue with Async IIFEs.
async function ping() {
for (let i = 0; i < 5; i++) {
let result = await Promise.resolve("ping");
console.log(result);
}
console.log("Why am I even here?");
}
async function pong() {
for (let i = 0; i < 5; i++) {
let result = await Promise.resolve("pong");
console.log(result);
}
console.log("I have nothing to do with any of this");
}
console.log("Let the games begin!");
ping();
pong();
console.log("Placeholder for code that is not related to ping pong");
In this example, the outside logs are logged first as part of the task of running the script, then the values of the resolved promises in the order that they were queued in the microtask queue. Within this entire process, the logs left underneath the for loops have nothing to do with the loops and are needlessly paused until the last microtask in their respective function bodies is out of the queue.
This is exactly what happens when we use async functions as IIFEs though. If you have code under the await that is meant to execute synchronously, it would have to needlessly wait until all the awaits above it have been checked out of the microtask queue.
I can see this being a problem if someone blindly wraps their entire express routes in async functions, where they would needlessly await the resolving of certain promises like database operations, the sending of emails, reading of files, etc..., So why do people still do this?
app.post('/forgotPwd', async (req, res) => {
const {email, username} = req.body;
if (!email) {
res.status(400).json({error: "No username entered"});
return;
}
if (!username) {
res.status(400).json({error: "No email entered"});
return;
}
const db = client.db();
const user = await db.collection("Users").findOne({username: username, "userInfo.email": email});
if (!user) {
res.status(400).json({error: "Account not found"});
return;
}
const authToken = await getAuthToken({id: user._id.toHexString()}, "15m");
// Would probably send a more verbose email
await sgMail.send({
from: process.env.EMAIL,
to: email,
subject: 'Forgot Password',
text: `Use this url to reset your password: http://localhost:5000/confirmation/passConf/${authToken}`,
});
res.json({error: ""});
});

If you want something in an async function to run synchronously, make sure it's prior to the first await in the function.
So why do people still do this?
That's probably an off-topic question for SO since it largely calls for opinion-based answers, but it's likely going to be either A) Because they don't want that code to run until the code above it has finished, or B) Because they don't understand async functions.

The point of using async/await is to make asynchronous code look synchronous because it's easier to read. In fact, it is a syntactic sugar that hides a callback hell underneath. You don't need to deal with callbacks in order to handle async operations.
In your case, if you think that the code after the for loop has nothing to do with the awaited operation, you shouldn't have place it after await. Or you should refactor the code so that it does not use await (callbacks).
As for the question of why people do this. Well, can you tell why people use .map() as a replacement of .forEach()? Or can you tell why they don't handle exceptions? They probably don't fully understand it or (as T.J. Crowder mentioned) they do want the code to run after the awaited operation. Simple as that.

Related

Benefits of using async await in simple functions

I have a simple function that aims to log a user in, and some guard clauses in it to rule out errors
async signIn(email: string, passwort: string): Promise<void> {
const user: IPerson = await this.fetchUser(email);
if (user === undefined) {
return Promise.reject('Wrong email');
}
if (user.passwort !== passwort) {
return Promise.reject('Wrong password');
}
if (!this.logInUser(user)) {
return Promise.reject('Login Failed');
}
return Promise.resolve();
}
I used async await to wait for the promise resolve of fetchUser giving me the user, but this made me think, what benefits does await have here?
As summarized in some blog this is how async works:
"Let’s emphasize: await literally suspends the function execution until the promise settles, and then resumes it with the promise result. That doesn’t cost any CPU resources, because the JavaScript engine can do other jobs in the meantime: execute other scripts, handle events, etc."
But in my case, there is no other other jobs in the meantime, the function can only resolve if the fetchUser provides something. Doesnt this mean JS Engine automatically waits and behaves just the same like using async? That would make it redundant.
what benefits does await have here?
It lets you write syntax that is easier to follow than using then with a callback.
That's all.
But in my case, there is no other other jobs in the meantime, the function can only resolve if the fetchUser provides something.
fetchUser is asynchronous. It isn't made asynchronous by the use of await. It provides a promise.
If you don't await that promise then you are just changing where the code execution is suspended.
Instead of signIn being suspended and the code that (you say) does nothing outside it continuing, you'll instead only suspend fetchUser and signIn will continue processing with the promise returned by fetchUser instead of the User object that that promise eventually resolves with.
Doesn't this mean JS Engine automatically waits and behaves just the same like using async? That would make it redundant.
No, the JS engine doesn't automatically "wait". Without await it would immediately continue executing the if statements, which obviously is not desired, as then you would not have the actual user object to work with, but "just" a promise object.
If the only thing that you wanted to do was to call fetch then it would be redundant to await it, but since you need the response from that request, you need to get informed when that response is available, and that is what await is offering you. You could alternatively chain a then call, whose callback would be called when the response becomes available.

What is the point of the await keyword within an async function in terms of returning a value?

I've researched many many posts and articles, and documentation. I'm seeking deeper understanding.
From https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/await
The await expression causes async function execution to pause until a Promise is settled, [...]
let fetchLocation = () => {
//fetches information from satellite system and
//returns a Vector2 of lat long on earth
return new Promise(resolve => {
setTimeout(() => {
resolve({
lat: 73,
long: -24
});
}, 2000);
});
}
//async functions automatically return a promise.
let main = async() => {
//awaits the promises resolution
let result = await fetchLocation();
console.log("I'm the awaited promise result", result)
//immediately returns a promise as promise pending
return result;
}
console.log(main().then(res => console.log("I'm the original promise, just passed along by the async function, it didn 't a-wait for me", res)))
Before going down this rabbit hole, I knew async/await was syntactical sugar to make async code look synchronous.
I therefore fully expected to see main return the lat long coordinates. Instead it returns a promise.
A couple questions.
Is the MDN documentation wrong? Does it not actually pause execution? It seems that the await keyword does not actually "pause" function execution... invoking main immediately returns a Promise<pending> object. However, after it returns we receive the value of the fulfilled promise in the awaited variable result. I suppose this came from the event loop? (And an async return statement is still synchronous.)
So it seems that await unwraps a promise, and async wraps a promise, but you need to use await within async. I get that await might actually be promise.then() underneath the hood but if they want to make it fully synchronous in appearance, why not have the async function return the resolved promise value when used with await? Otherwise async await seems a bit redundant.
Is the MDN documentation wrong?
No
Does it not actually pause execution?
It pauses the execution of the async function, hands a Promise back to the calling function — because it hasn't reached the point in the async function where it knows what to return — then the execution of the calling function (and the rest of the code) continues.
However, after it returns we receive the value of the fulfilled promise in the awaited variable result. I suppose this came from the event loop?
When the event loop stops being busy (running the code that called the async function in the first place and anything else that it picks up in the meantime) and the promise that was being awaited resolves, then the async function is woken up and given the resolved value of the function to continue processing.
why not have the async function return the resolved promise value when used with await?
Because that would block the entire event loop, which is why asynchronous logic (starting with callback style APIs) was introduced into JS in the first place.
Otherwise async await seems a bit redundant.
Consider a situation where you want to request a number of URLs from an API serially (so that you don't shove too many simultaneous requests at the API):
const data = [];
for (let i = 0; i < urls.length; i++) {
const response = await fetch(urls[i]);
const response_data = await response.json();
data.push(response_data);
}
If you were to rewrite that without await then you'd probably end up with something recursive to count your way through the loop. It would be significantly more complex.
async/await makes dealing with complex combinations of promises simple, and simple promises trivial.
knew async/await was syntactical sugar to make async code look synchronous.
And it does that inside the async function and only inside it.
It doesn't really pause the function, it just allows you to write the rest of the function code as if it were. But it's really just wrapping the rest of the code of the function into a .then().
E.g.
await foo = someFunc();
console.log(foo);
is executed like
someFunc().then((foo => {
console.log(foo);
});
Since it's really still asynchronous, you can't return the resolved value, because the original calling function returns immediately. But if the calling function is also declared async, it returns a Promise, and the caller can either use await again (so it appears to be synchronous) or use .then().

Using for await...of with synchronous iterables

MDN says for await...of has two use-cases:
The for await...of statement creates a loop iterating over async
iterable objects as well as on sync iterables,...
I was previously aware of the former: async iterables using Symbol.asyncIterator. But I am now interested in the latter: synchronous iterables.
The following code iterates over a synchronous iterable - an array of promises. It appears to block progess on the fulfilment of each promise.
async function asyncFunction() {
try {
const happy = new Promise((resolve)=>setTimeout(()=>resolve('happy'), 1000))
const sad = new Promise((_,reject)=>setTimeout(()=>reject('sad')))
const promises = [happy, sad]
for await(const item of promises) {
console.log(item)
}
} catch (err) {
console.log(`an error occurred:`, err)
}
}
asyncFunction() // "happy, an error occurred: sad" (printed in quick succession, after about 5 seconds)
The behavior appears to be akin to awaiting each promise in-turn, per the logic shown below. Is this assertion correct?
async function asyncFunction() {
try {
const happy = new Promise((resolve)=>setTimeout(()=>resolve('happy'), 1000))
const sad = new Promise((_,reject)=>setTimeout(()=>reject('sad')))
const promises = [happy, sad]
for(let p of promises) {
const item = await p
console.log(item)
}
} catch (err) {
console.log(`an error occurred:`, err)
}
}
asyncFunction() // "happy, an error occurred: sad" (printed in quick succession, after about 5 seconds)
I ask because this pattern of code has an implicit rejection wire-up pitfall that Promise.all and Promise.allSettled avoid, and it seems strange to me that this pattern would be explicitly supported by the language.
window.addEventListener('unhandledrejection', () => {
console.log('unhandled rejection; `sad` was not being awaited at the time it rejected')
})
async function asyncFunction() {
try {
const happy = new Promise((resolve)=>setTimeout(()=>resolve('success'), 1000))
const sad = new Promise((_,reject)=>setTimeout(()=>reject('failure')))
const promises = [happy, sad]
for(let p of promises) {
const item = await p
console.log(item)
}
} catch (err) {
console.log(`an error occurred:`, err)
}
}
asyncFunction() // "unhandled rejection; `sad` was not being awaited at the time it rejected" (after about zero seconds), and then "happy, an error occurred: sad" (printed in quick succession, after about 5 seconds)
Yes, it is strange, and you should not do this. Don't iterate arrays of promises, it leads exactly to the unhandled-rejections problem you mentioned. (See also this more specific explanation.)
So why is this supported in the language? To continue with the sloppy promise semantics.
You can find the exact reasoning in this comment of the issue discussing this part of the proposal:
I think we should fall back to Symbol.iterator because our current
Promise semantics are all about allowing sync things to be used as
async things. You might call this "sloppiness". It follows
#groundwater's logic above,
but I just want to spell out the parallels in more detail.
The "chaining" semantics of .then are all about this. You can return a
Promise from .then or a scalar value; it's all the same. You call
Promise.resolve not to wrap something in a Promise, but to cast
something to a Promise--get an asynchronous value when you have
something-or-other.
The semantics of async and await are all about being sloppy as well.
You can slap await on any non-Promise expression in an async function
and everything works fine, exactly the same way, except that you yield
control to the job queue. Similarly, you can "defensively" put async
around whatever you want, as long as you await the result. If you have
a function that returns a Promise--whatever! you can make that an
async function, and, from a user perspective, nothing changes (even
if, technically, you get a different Promise object out).
Async iterators and generators should work the same way. Just like you
can await a value that, accidentally, wasn't a Promise, a reasonable
user would expect to be able to yield* a sync iterator within an async
generator. for await loops should similarly "just work" if a user
defensively marks a loop that way, thinking that they maybe might be
getting an async iterator.
I think it would be a big deal to break all of these parallels. It
would make async iterators less ergonomic. Let's discuss this the next
time async generators/iterators come up on the agenda at TC39.
The sad promise isn't being awaited when it fails - that code needs to finish waiting on happy before it can begin to wait on sad. The sad promise is failing before happy resolves. (Promise.all is a tool better suited to this use-case)

Does an async function always need to be called with await?

First some context, I have an async function which logs messages on a MongoDB database.
async function log_message(sender, conversationId, text) {
try {
const msg = new Message({
sender: sender,
conversationId: conversationId,
text: text
});
await msg.save();
console.log("Done");
} catch (e) {
console.log("An error happened while logging the message: " + e)
}
}
Now, I have another async function that gets triggered when I receive a message and takes care of processing it and fetching some data.
As soon as this function get triggered I call "log_message" to log the message on my database, but I do not want to call it with await, otherwise, I would wait until the "log_message" function returns before processing the message slowing down the message processing.
async getReply(username, message) {
log_message("user", username, message);
console.log("HI");
let reply = await this.rs.reply(username, message, this);
return reply;
}
Nevertheless, Jetbrains Webstorm gives me this warning "Missing await for an async function call".
Now, I did some tests and if I call the function without await the system behaves as I expect, the message gets processed and my logging function writes the data on the db asynchronously without interrupting.
If instead, I put the await keyword before calling the logging function the execution of the code in the main function gets suspended until the db has not been written.
Could someone tell me whether there are some flaws in the way I intended the usage of the async/await keywords?
It is not necessary if your logic doesn't require the result of the async call. Although not needed in your case, the documentation lists two benefits to having that warning enabled:
While this is generally not necessary, it gives two main benefits.
The first one is that you won't forget to add 'await' when surrounding your code with try-catch.
The second one is that having explicit 'await' helps V8 runtime to provide async stack traces
When you set async to a method, the method expects to use await inside its scope. Because the functionality of async keyword is like if you were telling to the method that inside its scope there's another process which must be completed before can proceed to the method process.
In other words, if you don't want to await to some other process to finish, then you should avoid async keyword in your method.
Here, makes sense the await keyword:
await msg.save();
Because it awaits till it's saved before log that is Done. But in the methods you want all run without waiting for other processes to end. Just don't use asyn keyword. If you don't want to await here:
await this.rs.reply(username, message, this);
Don't use async here:
async getReply(username, message)
If you need to await something in getReply then you should add async keyword, otherwise, is not necessary the keyword.
Whenever you want to await some operation you have to associate await with that but before associate await with that you need to make parent function async.
Though I see you have answers here.
Let me tell you the thing now!
Async functions are not necessarily required to have an await call inside it, but the reason behind making a function async is that you need an asynchronous call for which you want to wait for the result before moving forward in the thread.
async getReply(username, message) {
log_message("user", username, message);
console.log("HI");
let reply = await this.rs.reply(username, message, this);
return reply;
}
The above function will work, but why exactly would you make it async? Since you aren't using any asynchronous functionality. It works the same if used as following:-
getReply(username, message) {
log_message("user", username, message);
console.log("HI");
let reply = await this.rs.reply(username, message, this);
return reply;
}

Move object/variable away from async function

I understand that this is a basic question, but I can't figure it out myself, how to export my variable "X" (which is actually a JSON object) out of "for" cycle. I have tried a various ways, but in my case function return not the JSON.object itself, but a "promise.pending".
I guess that someone more expirienced with this will help me out. My code:
for (let i = 0; i < server.length; i++) {
const fetch = require("node-fetch");
const url = ''+(server[i].name)+'';
const getData = async url => {
try {
const response = await fetch(url);
return await response.json();
} catch (error) {
console.log(error);
}
};
getData(url).then(function(result) { //promise.pending w/o .then
let x = result; //here is real JSON that I want to export
});
}
console.log(x); // -element is not exported :(
Here's some cleaner ES6 code you may wish to try:
const fetch = require("node-fetch");
Promise.all(
server.map((srv) => {
const url = String(srv.name);
return fetch(url)
.then((response) => response.json())
.catch((err) => console.log(err));
})
)
.then((results) => {
console.log(results);
})
.catch((err) => {
console.log('total failure!');
console.log(err);
});
How does it work?
Using Array.map, it transforms the list of servers into a list of promises which are executed in parallel. Each promise does two things:
fetch the URL
extract JSON response
If either step fails, that one promise rejects, which will then cause the whole series to reject immediately.
Why do I think this is better than the accepted answer? In a word, it's cleaner. It doesn't mix explicit promises with async/await, which can make asynchronous logic muddier than necessary. It doesn't import the fetch library on every loop iteration. It converts the server URL to a string explicitly, rather than relying on implicit coercion. It doesn't create unnecessary variables, and it avoids the needless for loop.
Whether you accept it or not, I offer it up as another view on the same problem, solved in what I think is a maximally elegant and clear way.
Why is this so hard? Why is async work so counterintuitive?
Doing async work requires being comfortable with something known as "continuation passing style." An asynchronous task is, by definition, non-blocking -- program execution does not wait for the task to complete before moving to the next statement. But we often do async work because subsequent statements require data that is not yet available. Thus, we have the callback function, then the Promise, and now async/await. The first two solve the problem with a mechanism that allows you to provide "packages" of work to do once an asynchronous task is complete -- "continuations," where execution will resume once some condition obtains. There is absolutely no difference between a boring node-style callback function and the .then of a Promise: both accept functions, and both will execute those functions at specific times and with specific data. The key job of the callback function is to act as a receptacle for data about the asynchronous task.
This pattern complicates not only basic variable scoping, which was your main concern, but also the issue of how best to express complicated workflows, which are often a mix of blocking and non-blocking statements. If doing async work requires providing lots of "continuations" in the form of functions, then we know that doing this work will be a constant battle against the proliferation of a million little functions, a million things needing names that must be unique and clear. This is a problem that cannot be solved with a library. It requires adapting one's style to the changed terrain.
The less your feet touch the ground, the better. :)
Javascript builds on the concept of promises. When you ask getData to to do its work, what is says is that, "OK, this is going to take some time, but I promise that I'll let you know after the work is done. So please have faith on my promise, I'll let you know once the work is complete", and it immediately gives you a promise to you.
That's what you see as promise.pending. It's pending because it is not completed yet. Now you should register a certain task (or function) with that promise for getData to call when he completes the work.
function doSomething(){
var promiseArray = [];
for (let i = 0; i < server.length; i++) {
const fetch = require("node-fetch");
const url = ''+(server[i].name)+'';
const getData = async url => {
try {
const response = await fetch(url);
return await response.json();
} catch (error) {
console.log(error);
}
};
promiseArray.push(getData(url)); // keeping track of all promises
}
return Promise.all(promiseArray); //see, I'm not registering anything to promise, I'm passing it to the consumer
}
function successCallback(result) {
console.log("It succeeded with " + result);
}
function failureCallback(error) {
console.log("It failed with " + error);
}
let promise = doSomething(); // do something is the function that does all the logic in that for loop and getData
promise.then(successCallback, failureCallback);

Categories

Resources