Benefits of using async await in simple functions - javascript

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.

Related

Why use async/await, it makes code work like sync

If you use promise's then() syntax, you can truly run codes concurrently.
See this example.
async function asyncFunc() {
return '2';
}
console.log('1')
asyncFunc().then(result => console.log(result))
console.log('3')
this prints 1 -> 3 -> 2 because asyncFunction() runs in "async" and rest of code ('3') runs without delay.
But if you use async/await syntax, doesn't it work like plain ordinary "sync" code?
async function asyncFunc() {
return '2';
}
console.log('1')
const result = await asyncFunc()
console.log(result)
console.log('3')
this would print 1->2->3 because it "defers" rest of the code to "await" asyncFunction()
So I'm curious about the purpose of async/await syntax. I guess it allows you to use async functions like sync function. With async/await, I can easily see how my code runs because it has the same flow as plain sync code. But then, why not just use sync code in the first place?
But I'm sure I'm missing something here right? What is the purpose of async/await syntax?
The idea of async/await is as follows:
It is a syntactic sugar for promises. It allows to use async code (like Web Api's - AJAX, DOM api's etc.) in synchronous order.
You ask "But then, why not just use sync code in the first place?" - because in your example you forced a sync code to be returned in a promise, basically you made it async, but it could run as sync in the first place, but there are Web APIs as mentioned above, that work ONLY ASYNC (meaning you have to wait for them to be executed), that's why you cannot turn them in sync "in the first place" :)
When JavaScript encounters the await expression, it pauses the async function execution and waits until the promise is fulfilled (the promise successfully resolved) or rejected (an error has occurred), then it continues the execution where it pauses in that function... The promise can be code from JS engine (as in your case) or it can be from another place like browser apis (truly async).

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().

Why is a function async when it calls another async function?

I thought I understood how to use async and await till I saw the below code snippet.
So there is an onInput event handler function attached to the movie input textbox. Within it is called the fetchData asynchronous function which uses the await keyword to wait for results from the axios.get function.
My question is, why do we need to make the onInput function also async? I mean, the fetchData function is async. Which means, it will wait till the axios.get is resolved and the execution will be paused till axios.get is resolved. So when the line const movies = fetchData(event.target.value); executes, the fetchData function will be executed which will pause on the await axios.get statement. So why do we need to use await while calling fetchData and make onInput async??
const fetchData = async(searchTerm)=>{
const results = await axios.get('http://www.omdbapi.com/', {
params:{
apikey:'d9835cc5',
s: searchTerm
}
});
return results.data.Search;
}
const movieInput = document.querySelector('input');
const onInput = async event => {
const movies = await fetchData(event.target.value);
console.log(movies);
}
movieInput.addEventListener('input', onInput);
I mean, the fetchData function is async. Which means, it will wait till the axios.get is resolved and the execution will be paused till axios.get is resolved.
This concept is hiding a lot of detail that may be confusing, such as that paused execution is resumed using callback functions.
async functions do not pause execution of their callers.
Instead they return a promise that will be resolved with the value syntactically returned from executing the async function body. To be clear, the value apparently returned from within an async function body is not returned to the caller directly - the caller gets a promise for it.
When executed the await operator sets up call backs for when its operand promise becomes settled. Effectively it calls the then method of its promise operand to supply a set of onfulfilled and onrejected callbacks, before storing the current execution context in memory and returning to the event loop.
When it receives a callback from promise settlement, the await operator restores the execution context it previously saved. If the awaited promise is rejected, await throws the rejection reason. If fulfilled, await resumes exection and returns the fulfilled value of the promise as the result of executing the await operator.
Now historically await was never a reserved keyword - not mentioned in ES3, future reserved keyword in ES6 (ECMAScript 2015) but reserved word in the current draft of ECMAscript as at May 2021.
Hence, to retain compatibility with code on the web, await was only recognized as an operator if it occurs within an async function - leaving it available as an identifier outside of async function bodies.
Likewise the async keyword was not historically reserved, but was able to be introduced without comparability issues by requiring its usage in a position that would have produced an unexpected identifier error in previous versions of JavaScript. Meaning before the function keyword or before an arrow function expression.
Finally you need to declare onInput as an async function because await is being used as an operator within its body. Without the async declaraton, await will be treated as an identifier rather than the name of an operator.
As a side note, the promise returned by onInput is being discarded and could generate an uncaught promise rejection error in its current form.
To answer the slightly different question of "why does await need to be used in the onInput function at all?", the value returned from fetchData is a pending promise. In order to set movies to the results.data.Search value obtained within fetchData, you need to wait for the fulfilled value of the returned promise. Using async/await is one means of doing so. The other is to add a fulfillment handler to the promise returned from fetchData by calling its then method.
You are using the await operator inside inInput. It's a JavaScript rule that functions using await must be async, so inInput must also be async. Have a look at this answer: https://stackoverflow.com/a/56685416/13357440
Also, MDN has useful information: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/await
In general, you would want API calls to execute while the rest of your program runs, so the onInput would be async. Furthermore, javascript mandates that an await is inside of an async function, so the code was simply following its standards and rules as well. Typically, once the top level is an async, everything inside of it is just following the structure. More info can be found here.

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

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.

Firing Vuex actions asynchronously and sequentially - what am I not understanding?

I have a Vuex store and I am trying to fetch data from the Firebase Realtime Database. I am initially fetching the user information, however afterwards I would like to fetch some other information that relies upon the initial data fetched.
As you can see from the code, I am trying to do this using async / await, however whenever firing the two actions in my created() hook, the user's information isn't initialised, and therefore the second action fails.
My user store
async fetchCreds({ commit }) {
try {
firebase.auth().onAuthStateChanged(async function(user) {
const { uid } = user
const userDoc = await users.doc(uid).get()
return commit('SET_USER', userDoc.data())
})
} catch (error) {
console.log(error)
commit('SET_USER', {})
}
}
My club action which relies upon the above call
async fetchClubInformation({ commit, rootState }) {
try {
const clubIDForLoggedInUser = rootState.user.clubId
const clubDoc = await clubs.doc(clubIDForLoggedInUser).get()
return commit('SET_CLUB_INFO', clubDoc.data())
} catch (error) {
console.log(error)
}
}
}
The methods being called within my component's created() method.
created: async function() {
await this.fetchCreds();
await this.fetchClubInformation();
this.loading = false;
}
I have a feeling I'm fundamentally misunderstanding async / await, but I can't understand what in the code is incorrect - any help or advice would be greatly appreciated.
I'm not particularly familiar with Firebase but after a bit of digging through the source code I think I can shed a little light on your problems.
Firstly, consider the following example:
async function myFn (obj) {
obj.method(function () {
console.log('here 1')
})
console.log('here 2')
}
await myFn(x)
console.log('here 3')
Question: What order will you see the log messages?
Well here 2 will definitely come before here 3 but it's impossible to tell from the code above when here 1 will show up. It depends on what obj.method does with the function it's been passed. It might never call it at all. It might call it synchronously (e.g. Array's forEach method), in which case here 1 will appear before the other messages. If it's asynchronous (e.g. timers, server calls) then here 1 may not show up for some time, long after here 3.
The async modifier will implicitly return a Promise from the function if it doesn't return a Promise itself. The resolved value of that Promise will be the value returned from the function and the Promise will resolve at the point the function returns. For a function without a return at the end that's equivalent to it finishing with return undefined.
So, to stress the key point, the Promise returned by an async function will only wait until that function returns.
The method onAuthStateChanged calls its callback asynchronously, so the code in that callback won't run until after the surrounding function has completed. There's nothing to tell the implicitly returned Promise to wait for that callback to be invoked. The await inside the callback is irrelevant as that function hasn't even been called yet.
Firebase makes extensive use of Promises, so typically the solution would just be to return or await the relevant Promise:
// Note: This WON'T work, explanation follows
return firebase.auth().onAuthStateChanged(async function(user) {
// Note: This WON'T work, explanation follows
await firebase.auth().onAuthStateChanged(async function(user) {
This won't work here because onAuthStateChanged doesn't actually return a Promise, it returns an unsubscribe function.
You could, of course, create a new Promise yourself and 'fix' it that way. However, creating new Promises using new Promise is generally considered a code smell. Typically it's only necessary when wrapping code that doesn't support Promises properly. If we're working with a library that has proper Promise support (as we are here) then we shouldn't need to create any Promises.
So why doesn't onAuthStateChanged return a Promise?
Because it's a way of watching all sign-in/sign-out events. Every time the user signs in or signs out it'll call the callback. It isn't intended as a way to watch a particular sign-in. A Promise can only be resolved once, to a single value. So while a single sign-in event could be modelled with a Promise it's meaningless when watching all sign-in/sign-out events.
So fetchCreds is registering to be notified about all sign-in/sign-out events. It doesn't do anything with the returned unsubscribe function, so presumably it'll be listening to all such events until the page is reloaded. If you call fetchCreds multiple times it'll keep adding more and more listeners.
If you're waiting for a user to finish signing in then I suggest waiting for that directly instead. firebase.auth() has various methods starting with the prefix signIn, e.g. signInWithEmailAndPassword, and these do return a Promise that resolves when the user has finished signing in. The resolved value provides access to various information, including the user. I don't know which method you're using but the idea is much the same for all of them.
However, it might be that you're really just interested in grabbing the details of the current user. If that's all you want then you don't need to use onAuthStateChanged at all. You should just be able to grab a copy using the currentUser property. Something like this:
async fetchCreds({ commit }) {
try {
const { uid } = firebase.auth().currentUser
const userDoc = await users.doc(uid).get()
commit('SET_USER', userDoc.data())
} catch (error) {
console.log(error)
commit('SET_USER', {})
}
}
As I've already mentioned, this relies on the assumption that the user is already signed in. If that isn't a safe assumption then you might want to consider waiting until after sign in has completed before creating components that need user credentials.
Update:
Questions from the comments:
If the obj.method() call was asynchronous and we did await the callback function within it, would that ensure that the outer async function (myFn) never resolves before the inner one has finished?
I'm not entirely sure what you're asking here.
Just to be clear, I'm being very careful with my use of the words async and asynchronous. A function such as setTimeout would be considered asynchronous but it is not async.
async/await is just a lot of syntactic sugar around Promises. You don't really wait for a function, you wait for a Promise. When we talk about awaiting an async function we're really talking about waiting for the Promise it returns to resolve.
So when you say await the callback function it's not really clear what that means. Which Promise are you trying to await?
Putting the async modifier on a function doesn't make it magically wait for things. It will only wait when it encounters await. You can still have other asynchronous calls within an async function and, just like with a normal function, these calls will be performed after the function has returned. The only way to 'pause' is to await a Promise.
Putting an await inside another function, even a nested function, won't make any difference to whether the outer function waits unless the outer function is already waiting for the inner function. Behind the scenes this is all just Promises chaining then calls. Whenever you write await you're just adding another then call to a Promise. However, that won't have the desired effect unless that Promise is in the same chain as the Promise returned by the outer async function. It only needs one link to be missing for the chain to fail.
So modifying my earlier example:
async function myFn (obj) {
await obj.method(async function () {
await somePromise
// ...
})
// ...
}
await myFn(x)
Note that there are 3 functions here: myFn, method and the callback passed to method. The question is, will await myFn(x) wait for somePromise?
From the code above we can't actually tell. It would depend on what method does internally. For example, if method looked like this then it still wouldn't work:
function method (callback) {
setTimeout(callback, 1000)
}
Putting async on method won't help, that'll just make it return a Promise but the Promise still won't be waiting for the timer to fire.
Our Promise chain has a broken link. myFn and the callback are both creating their parts of the chain but unless method links those Promises together it won't work.
On the other hand, if method is written to return a suitable Promise that waits for the callback to complete then we will get our target behaviour:
function method (callback) {
return someServerCallThatReturnsAPromise().then(callback)
}
We could have used async/await here instead but there was no need as we can just return the Promise directly.
Also, if in the async myFn function you're not returning anything, does that mean it'll resolve immediately and as undefined?
The term immediately is not well-defined here.
If a function isn't returning anything at the end then it's equivalent to having return undefined at the end.
The Promise returned by an async function will resolve at the point the function returns.
The resolved value for the Promise will be the value returned.
So if you aren't returning anything it will resolve to undefined. Resolving won't happen until the end of the function is reached. If the function doesn't contain any await calls then this will happen 'immediately' in the same sense as a synchronous function returning 'immediately'.
However, await is just syntactic sugar around a then call, and then calls are always asynchronous. So while the Promise might resolve 'immediately' the await still has to wait. It's a very short wait, but it isn't synchronous and other code may get the opportunity to run in the meantime.
Consider the following:
const myFn = async function () {
console.log('here 3')
}
console.log('here 1')
Promise.resolve('hi').then(() => {
console.log('here 4')
})
console.log('here 2')
await myFn()
console.log('here 5')
The log messages will appear in the order they're numbered. So even though myFn resolves 'immediately' you'll still get here 4 jumping in between here 3 and here 5.
To make it short
fetchCreds({ commit }) {
return new Promise((resolve, reject) => {
try {
firebase.auth().onAuthStateChanged(async function(user) {
const { uid } = user
const userDoc = await users.doc(uid).get()
commit('SET_USER', userDoc.data())
resolve()
})
} catch (error) {
console.log(error)
commit('SET_USER', {})
resolve()
}}
}
async () => undefined // returns Promise<undefined> -> undefined resolves immediatly
asnyc () => func(cb) // returns Promise<any> resolves before callback got called
() => new Promise(resolve => func(() => resolve())) // resolves after callback got called

Categories

Resources