Your Web News in One Place

Help Webnuz

Referal links:

Sign up for GreenGeeks web hosting
July 16, 2022 02:42 pm GMT

JavaScript Promises and Async Await

In the previous article we have seen what JavaScript callbacks are and what potential issues they have. In this article
we are going to explore JavaScript Promises. How they work, potential issues with promises and, how async and await solves these issues. If you haven't read the previous article I recommend you do. JavaScript Callbacks Article

Promises

Have you ever been to a busy restaurant without a reservation? When this happens, the restaurant needs a way to get back in
contact with you when a table opens up. Historically, they'd just take your name and yell it when your table was ready. Then,
as naturally occurs, they decided to start getting fancy. One solution was, instead of taking your name, they'd take your
number and text you once a table opened up. This allowed you to be out of yelling range but more importantly, it allowed them
to target your phone with ads whenever they wanted. Sound familiar? It should! OK, maybe it shouldn't. It's a metaphor for
callbacks! Giving your number to a restaurant is just like giving a callback function to a third party service. Youexpectthe restaurant to text you when a table opens up, just like youexpectthe third party service to invoke your function when and how they said they would.
Once your number or callback function is in their hands though, you've lost all control.

Thankfully, there is another solution that exists. One that, by design, allows you to keep all the control. You've
probably even experienced it before - it's that little buzzer thing they give you. You know, this one.

Buzzer

If you've never used one before, the idea is simple. Instead of taking your name or number, they give you this
device. When the device starts buzzing and glowing, your table is ready. You can still do whatever you'd like as
you're waiting for your table to open up, but now you don't have to give up anything. In fact, it's the
exact opposite.Theyhave to giveyousomething. There is no inversion of control.

The buzzer will always be in one of three different states -pending,fulfilled, orrejected.

pendingis the default, initial state. When they give you the buzzer, it's in this state.

fulfilledis the state the buzzer is in when it's flashing and your table is ready.

rejectedis the state the buzzer is in when something goes wrong. Maybe the restaurant is about to
close or they forgot someone rented out the restaurant for the night.

Again, the important thing to remember is that you, the receiver of the buzzer, have all the control. If the
buzzer gets put intofulfilled, you can go to your table. If it gets put intofulfilledand you
want to ignore it, cool, you can do that too. If it gets put intorejected, that sucks but you can go
somewhere else to eat. If nothing ever happens and it stays inpending, you never get to eat but you're
not actually out anything.

Now that you're a master of the restaurant buzzer thingy, let's apply that knowledge to something that matters.

If giving the restaurant your number is like giving them a callback function, receiving the little buzzy thing is like receiving what's called a "Promise".

As always, let's start withwhy. Why do Promises exist? They exist to make the complexity of making
asynchronous requests more manageable. Exactly like the buzzer, aPromisecan be in one of three
states,pending,fulfilledorrejected. Unlike the buzzer, instead of these states representing
the status of a table at a restaurant, they represent the status of an asynchronous request.

If the async request is still ongoing, thePromisewill have a status ofpending. If the async request
was successfully completed, thePromisewill change to a status offulfilled. If the async request
failed, thePromisewill change to a status ofrejected. The buzzer metaphor is pretty spot on, right?

Now that you understand why Promises exist and the different states they can be in, there are three more questions
we need to answer.

  1. How do you create a Promise?
  2. How do you change the status of a promise?
  3. How do you listen for when the status of a promise changes?

1) How do you create a Promise?

This one is pretty straight forward. You create anewinstance ofPromise.

const promise = new Promise()

2) How do you change the status of a promise?

ThePromiseconstructor function takes in a single argument, a (callback) function. This function is going
to be passed two arguments,resolveandreject.

resolve- a function that allows you to change the status of the promise tofulfilled

reject- a function that allows you to change the status of the promise torejected.

In the code below, we usesetTimeoutto wait 2 seconds and then invokeresolve. This will change the
status of the promise tofulfilled.

const promise = new Promise((resolve, reject) => {    setTimeout(() => {        resolve() // Change status to 'fulfilled'    }, 2000)})

We can see this change in action by logging the promise right after we create it and then again roughly
2 seconds later afterresolvehas been called.

resolve gif

Notice the promise goes from<pending>to<resolved>.

3) How do you listen for when the status of a promise changes?

In my opinion, this is the most important question. It's cool we know how to create a promise and change its
status, but that's worthless if we don't know how to do anything after the status changes.

One thing we haven't talked about yet is what a promise actually is. When you create anew Promise, you're
really just creating a plain old JavaScript object. This object can invoke two methods,then, andcatch.
Here's the key. When the status of the promise changes tofulfilled, the function that was passed to.thenwill
get invoked. When the status of a promise changes torejected, the function that was passed to.catchwill be
invoked. What this means is that once you create a promise, you'll pass the function you want to run if the async request
is successful to.then. You'll pass the function you want to run if the async request fails to.catch.

Let's take a look at an example. We'll usesetTimeoutagain to change the status of the promise tofulfilledafter
two seconds (2000 milliseconds).

function onSuccess() {    console.log("Success!")}function onError() {    console.log("")}const promise = new Promise((resolve, reject) => {    setTimeout(() => {        resolve()    }, 2000)})promise.then(onSuccess)promise.catch(onError)

If you run the code above you'll notice that roughly 2 seconds later, you'll see "Success!" in the console. Again
the reason this happens is because of two things. First, when we created the promise, we invokedresolveafter
~2000 milliseconds - this changed the status of the promise tofulfilled. Second, we passed theonSuccessfunction
to the promises'.thenmethod. By doing that we told the promise to invokeonSuccesswhen the status of the
promise changed tofulfilledwhich it did after ~2000 milliseconds.

Now let's pretend something bad happened and we wanted to change the status of the promise torejected.
Instead of callingresolve, we would callreject.

function onSuccess() {    console.log("Success!")}function onError() {    console.log("")}const promise = new Promise((resolve, reject) => {    setTimeout(() => {        reject()    }, 2000)})promise.then(onSuccess)promise.catch(onError)

Now this time instead of theonSuccessfunction being invoked, theonErrorfunction will be invoked since we calledreject.

Now that you know your way around the Promise API, let's start looking at some real code.

Remember the last async callback example we saw earlier?

function getUser(id, onSuccess, onFailure) {    $.getJSON({        url: `https://api.github.com/users/${id}`,        success: onSuccess,        error: onFailure,    })}function getWeather(user, onSuccess, onFailure) {    $.getJSON({        url: getLocationURL(user.location.split(",")),        success: onSuccess,        error: onFailure,    })}$("#btn").on("click", () => {    getUser(        "endalk200",        (user) => {            getWeather(                user,                (weather) => {                    updateUI({ user, weather: weather.query.results })                },                showError            )        },        showError    )})

Is there any way we could use the Promise API here instead of using callbacks? What if we wrap our AJAX
requests inside of a promise? Then we can simplyresolveorrejectdepending on how the request goes.
Let's start withgetUser.

function getUser(id) {    return new Promise((resolve, reject) => {        $.getJSON({            url: `https://api.github.com/users/${id}`,            success: resolve,            error: reject,        })    })}

Nice. Notice that the parameters ofgetUserhave changed. Instead of receivingid,onSuccess, a
ndonFailure, it just receivesid. There's no more need for those other two callback functions because we're no
longer inverting control. Instead, we use the Promise'sresolveandrejectfunctions.resolvewill be invoked
if the request was successful,rejectwill be invoked if there was an error.

Next, let's refactorgetWeather. We'll follow the same strategy here. Instead of taking inonSuccessandonFailurecallback
functions, we'll useresolveandreject.

function getWeather(user) {    return new Promise((resolve, reject) => {        $.getJSON({            url: getLocationURL(user.location.split(",")),            success: resolve,            error: reject,        })    })}

Looking good. Now the last thing we need to update is our click handler. Remember, here's the flow we want to take.

  1. Get the user's information from the Github API.
  2. Use the user's location to get their weather from the Yahoo Weather API.
  3. Update the UI with the user's info and their weather.

Let's start with #1 - getting the user's information from the Github API.

$("#btn").on("click", () => {    const userPromise = getUser("endalk200")    userPromise.then((user) => {})    userPromise.catch(showError)})

Notice that now instead ofgetUsertaking in two callback functions, it returns us a promise that we can call.thenand.catchon.
If.thenis called, it'll be called with the user's information. If.catchis called, it'll be called with the error.

Next, let's do #2 - Use the user's location to get their weather.

$("#btn").on("click", () => {    const userPromise = getUser("endalk200")    userPromise.then((user) => {        const weatherPromise = getWeather(user)        weatherPromise.then((weather) => {})        weatherPromise.catch(showError)    })    userPromise.catch(showError)})

Notice we follow the exact same pattern we did in #1 but now we invokegetWeatherpassing it theuserobject we
got fromuserPromise.

Finally, #3 - Update the UI with the user's info and their weather.

$("#btn").on("click", () => {    const userPromise = getUser("endalk200")    userPromise.then((user) => {        const weatherPromise = getWeather(user)        weatherPromise.then((weather) => {            updateUI({ user, weather: weather.query.results })        })        weatherPromise.catch(showError)    })    userPromise.catch(showError)})

Our new code isbetter, but there are still some improvements we can make. Before we can make those improvements though, there are two
more features of promises you need to be aware of, chaining and passing arguments fromresolvetothen.

Chaining

Both.thenand.catchwill return a new promise. That seems like a small detail but it's important because
it means that promises can be chained.

In the example below, we callgetPromisewhich returns us a promise that will resolve in at least 2000 milliseconds.
From there, because.thenwill return a promise, we can continue to chain our.thens together until we
throw anew Errorwhich is caught by the.catchmethod.

function getPromise() {    return new Promise((resolve) => {        setTimeout(resolve, 2000);    });}function logA() {    console.log("A");}function logB() {    console.log("B");}function logCAndThrow() {    console.log("C");    throw new Error();}function catchError() {    console.log("Error!");}getPromise()    .then(logA); // A    .then(logB) // B    .then(logCAndThrow) // C    .catch(catchError); // Error!

Cool, but why is this so important? Remember back in the callback section we talked about one of the downfalls of callbacks
being that they force you out of your natural, sequential way of thinking. When you chain promises together, it doesn't force
you out of that natural way of thinking because chained promises are sequential.getPromise runs then logA runs then logB runs then....

Just so you can see one more example, here's a common use case when you use thefetchAPI.fetchwill return you
a promise that will resolve with the HTTP response. To get the actual JSON, you'll need to call.json. Because of chaining,
we can think about this in a sequential manner.

fetch("/api/user.json")    .then((response) => response.json())    .then((user) => {        // user is now ready to go.    })

Now that we know about chaining, let's refactor ourgetUser/getWeathercode from earlier to use it.

function getUser(id) {    return new Promise((resolve, reject) => {        $.getJSON({            url: `https://api.github.com/users/${id}`,            success: resolve,            error: reject,        })    })}function getWeather(user) {    return new Promise((resolve, reject) => {        $.getJSON({            url: getLocationURL(user.location.split(",")),            success: resolve,            error: reject,        })    })}$("#btn").on("click", () => {    getUser("endalk200")        .then(getWeather)        .then((weather) => {            // We need both the user and the weather here.            // Right now we just have the weather            updateUI() // ????        })        .catch(showError)})

Itlooksmuch better, but now we're running into an issue. Can you spot it? In the second.thenwe want to callupdateUI.
The problem is we need to passupdateUIboth theuserand theweather. Currently, how we have it set up,
we're only receiving theweather, not theuser. Somehow we need to figure out a way to make it so the promise
thatgetWeatherreturns is resolved with both theuserand theweather.

Here's the key.resolveis just a function. Any arguments you pass to it will be passed along to the function given to.then.
What that means is that inside ofgetWeather, if we invokeresolveourself, we can pass to itweatheranduser.
Then, the second.thenmethod in our chain will receive bothuserandweatheras an argument.

function getWeather(user) {    return new Promise((resolve, reject) => {        $.getJSON({            url: getLocationURL(user.location.split(",")),            success(weather) {                resolve({ user, weather: weather.query.results })            },            error: reject,        })    })}$("#btn").on("click", () => {    getUser("endalk200")        .then(getWeather)        .then((data) => {            // Now, data is an object with a            // "weather" property and a "user" property.            updateUI(data)        })        .catch(showError)})

You can play around with thefinal code here

It's in our click handler where you really see the power of promises shine compared to callbacks.

// Callbacks getUser(    "endalk200",    (user) => {        getWeather(            user,            (weather) => {                updateUI({ user, weather: weather.query.results })            },            showError        )    },    showError)// Promises getUser("endalk200")    .then(getWeather)    .then((data) => updateUI(data))    .catch(showError)

Following that logic feels natural because it's how we're used to thinking, sequentially.getUser then getWeather then update the UI with the data.

Now it's clear that promises drastically increase the readability of our asynchronous code, but is there a way
we can make it even better? Assume that you were on the TC39 committee and you had all the power to add new features to the
JavaScript language. What steps, if any, would you take to improve this code?

$("#btn").on("click", () => {    getUser("endalk200")        .then(getWeather)        .then((data) => updateUI(data))        .catch(showError)})

As we've discussed, the code reads pretty nicely. Just as our brains work, it's in a sequential order. One issue that we did run
into was that we needed to thread the data (users) from the first async request all the way through to the last.then.
This wasn't a big deal, but it made us change up ourgetWeatherfunction to also pass alongusers. What if we just
wrote our asynchronous code the same way which we write our synchronous code? If we did, that problem would go away entirely and it
would still read sequentially. Here's an idea.

$("#btn").on("click", () => {    const user = getUser("endalk200")    const weather = getWeather(user)    updateUI({ user, weather })})

Well, that would be nice. Our asynchronous code looks exactly like our synchronous code. There's no extra steps our brain needs
to take because we're already very familiar with this way of thinking. Sadly, this obviously won't work. As you know, if we
were to run the code above,userandweatherwould both just be promises since that's whatgetUserandgetWeather
return. But remember, we're on TC39. We have all the power to add any feature to the language we want. As is, this code would be really
tricky to make work. We'd have to somehow teach the JavaScript engine to know the difference between asynchronous function invocations
and regular, synchronous function invocations on the fly. Let's add a few keywords to our code to make it easier on the engine.

First, let's add a keyword to the main function itself. This could clue the engine to the fact that inside of this function, we're
going to have some asynchronous function invocations. Let's useasyncfor this.

$("#btn").on("click", async () => {    const user = getUser("endalk200")    const weather = getWeather(user)    updateUI({ user, weather })})

Cool. That seems reasonable. Next let's add another keyword to let the engine know exactly when a function being invoked is
asynchronous and is going to return a promise. Let's useawait. As in, "Hey engine. This function is asynchronous
and returns a promise. Instead of continuing on like you typically do, go ahead and 'await' the eventual value of the
promise and return it before continuing". With both of our newasyncandawaitkeywords in play, our new code
will look like this.

$("#btn").on("click", async () => {    const user = await getUser("endalk200")    const weather = await getWeather(user.location)    updateUI({ user, weather })})

Pretty slick. We've invented a reasonable way to have our asynchronous code look and behave as if it were synchronous.
Now the next step is to actually convince someone on TC39 that this is a good idea. Lucky for us, as you probably guessed
by now, we don't need to do any convincing because this feature is already part of JavaScript and it's calledAsync/Await.

async functions return a promise

Now that you've seen the benefit of Async/Await, let's discuss some smaller details that are important to know. First, anytime you addasyncto a function, that function is going to implicitly return a promise.

async function getPromise() {}const promise = getPromise()

Even thoughgetPromiseis literally empty, it'll still return a promise since it was anasyncfunction.

If theasyncfunction returns a value, that value will also get wrapped in a promise. That means you'll have
to use.thento access it.

async function add(x, y) {    return x + y}add(2, 3).then((result) => {    console.log(result) // 5})

await without async is bad

If you try to use theawaitkeyword inside of a function that isn'tasync, you'll get an error.

$("#btn").on("click", () => {    const user = await getUser("endalk200"); // SyntaxError: await is a reserved word    const weather = await getWeather(user.location); // SyntaxError: await is a reserved word    updateUI({ user, weather });});

Here's how I think about it. When you addasyncto a function it does two things. It makes it so the
function itself returns (or wraps what gets returned in) a promise and makes it so you can useawaitinside of it.

Error Handling

You may have noticed we cheated a little bit. In our original code we had a way to catch any errors using.catch.
When we switched to Async/Await, we removed that code. With Async/Await, the most common approach is to wrap your code
in atry/catchblock to be able to catch the error.

$("#btn").on("click", async () => {    try {        const user = await getUser("endalk200")        const weather = await getWeather(user.location)        updateUI({ user, weather })    } catch (e) {        showError(e)    }})

That concludes our two part series on JavaScript callbacks and, async and await. If you have questions or feedback I would appreciate it if you leave a comment below.


Original Link: https://dev.to/endalk200/javascript-promises-and-async-await-192m

Share this article:    Share on Facebook
View Full Article

Dev To

An online community for sharing and discovering great ideas, having debates, and making friends

More About this Source Visit Dev To