Understanding JavaScript async/await in 7 seconds

twitter logo github logo Updated on ・1 min read

Have you been struggling to learn the new Async/Await syntax in ES2015? Well, Good news, here is a short 7 seconds animations to help you visually understand the difference between the old-good callback pattern and the new Async/Await syntax. Enjoy!

twitter logo DISCUSS (25)
markdown guide
 

Callback hell -> promise chaining -> async await (in an IIFE)

Next step is understanding Observables in 10 seconds (which isn't possible unfortunately 😒 )

But all kidding aside nice callback to an older twitter post ;D

 
getData(a).pipe(
  concatMap(b => getMoreData(b)),
  concatMap(c => getMoreData(c)),
  concatMap(d => getMoreData(d)),
  concatMap(e => getMoreData(e))
).subscribe({ next: e => console.log(e)})
 

Thank you. A tweet worths thousand words, right? _^

 

I didnt get it in 7 seconds. Not even after 3 minutes

 

I too struggle with async programming. It's not just about the code, but the mindframe. I recommend reading the Async & Performance book in the You Don't Know JS series. It's helped me a lot to understand the concepts and logic behind async.

 
 

I will get back to this in the weekend will definetly ping you for more explanations.

 

How do you implement the following with async/await:

getProfile(this.id).then(data => { this.profile = data; });

getComments(this.id).then(data => { this.comments = data; });

getFriends(this.id).then(data => { this.friends = data; });

Be aware that I can't use Promise.all because that would wait for all promises and require that all promises are resolved successfully.
What I want is to set the variables when a promise finishes and not wait for the others.

 
(async () => {
   this.profile = await getProfile(this.id);
   this.comments = await getProfile(this.id);
   this.friends = await getProfile(this.id);
})();
 

It should be in async function, then call it just like:

this.profile = await getProfile(this.id);
this.comments = await getComments(this.id);
this.friends = await getFriends(this.id);

 

The problem with this approach is that getProfile is blocking getComments and getFriends. Once getProfile is resolved successfully, getComments runs and blocking getFriends. When getComments is resolved successfully, getFriends finally runs.

That is not the same thing as running them parallel. That is more like the following:

getProfile(this.id).then(profileData => {
  this.profile = profileData;
  getComments(this.id).then(commentsData => {
    this.comments = commentsData;
    getFriends(this.id).then(friendsData => {
      this.friends = friendsData;
    });
  });
});

Ah sorry, I didn't read it correctly.

Yeah, promise.all wont resolve with a single reject, so what I would do is return an error as a normal result instead of rejecting, and then filter out errors after promises finish. Until they make something like .every available for .all as well.

And while it might be strange to have errors passing into resolve, as long as your code is expecting them, I see nothing wrong in it.

Hey, so for your particular use case, I would try this:

const profilePromise = getProfile(this.id);
const commentsPromise = getComments(this.id)
const friendsPromise = getFriends(this.id);

this.profile = await profilePromise;
this.comments = await commentsPromise;
this.friends = await friendsPromise;

Here is a working proof of concept:

const func = (t, f) => new Promise( (res, rej) => { setTimeout( () => res(f()), t )  } )
const a = func(2000, () => console.log('func 1'));
const b = func(0, () => console.log('func 2'));
const c = func(0, () => console.log('func 3'));
const [f1, f2, f3] = [await a, await b, await c];

console.log(f1, f2, f3);

To run them parallel there is this approach:

const result = await Promise.all([
  getProfile(this.id), 
  getComments(this.id),
  getFriends(this.id),
]);

However, this waits for all to finish and does not replace the first I descried, as I see it?

As I see it the only way to avoid this with async/await is to wrap the promises into their own function and run it in Promise.all. Like this (haven't tested this yet):

await Promise.all([
  async () => { this.profile = await getProfile(id); },
  async () => { this.comments = await getComments(id); },
  async () => { this.friends = await getFriends(id); },
]);

Hi,
As I see it, in both cases the inner methods run async and the end promise is awaited so it would not be blocking the current thread anyway.
When I get time, will execute 2nd e.g. but highly doubt if it has any difference in behavior compared to 1st.

 

To this day, I fail to see the wisdom of writing b => getMoreData(b), when mere getMoreData means exactly the same thing. Because really

getData()
  .then(a => getMoreData(a)) // repeat four times

is an equivalent of

const getMoreDataButWrappedWithOneMoreFunction = x => getMoreData(x);

getData()
   .then(getMoreDataButWrappedWithOneMoreFunction) // repeat four times

What is the point of getMoreDataButWrappedWithOneMoreFunction, I wonder? Why not just put getMoreData there?

It seems as if the moment a developer learn that in JS he can define a function in the middle of some expression, he immediately forgets he isn't obliged to.

 

Hey Maciej, thanks for reaching out.

So to give you some context, the code sample in the animation is meant to be easy for beginners to understand and it is not supposed to be too clever. So this was done on purpose in order to show the flow of all the parameters.

However, if you still prefer passing function references instead of function invocations, I have already made another animation illustrating this:

Cheers.

 

We still need to wrap the method in an arrow function to keep this in the correct context, unfortunately.

class myClass {
    myPrivateVal = "appendedValue";
    appendMyPrivateVal(argument) {
        return argument + ' ' + this.myPrivateVal;
    }
}
async function someAsyncMethod() {
    return new Promise<string>((resolve) => setTimeout(() => resolve("asyncResult"), 1000));
}
var myInstance = new myClass();
(async () => {
    const result = someAsyncMethod().then(myInstance.appendMyPrivateVal);
    result; // asyncResult undefinedβ€ˆ
    const result2 = someAsyncMethod().then((arg) => myInstance.appendMyPrivateVal(arg));
    result2; // asyncResult appendedValue
})();

I really hate this sometimes.

 
 
 
 

Callbacks and Async are a struggle. Each item in itself is easy to grasp, however when it's a whole lot of callbacks....

 
 
 

Very nice! It moves a bit fast though. Just making a slow-motion version would help me grok it in real time, and I already had a pretty good grip on async/await from my time with Dart.

Classic DEV Post from Mar 2

Which game are you playing right now?

What game are you playing right now? Do you have any favourites from your childhood that you reminisce about?

Wassim Chegham profile image
@Angular team β˜… Sr. Developer Advocate at Microsoft β˜… Creator of xlayers.dev, ngx.tools, autocap.cc, hexa.run, letme.vision, async-await.xyz β˜… GDE for Action On Google, GCP teams at Google

Creating your DEV account literally takes 30 seconds and is one of the best moves you can make for a happy and healthy software career.

Get started now ❀️