May 6th, 2024 × #javascript#promises#async#await
JS Promises Fundamentals - Part 1
A 3-part series on JavaScript promises. Part 1 covers the basics of what promises are, creating promises, and waiting on promises to resolve.
- Promises help handle asynchronous actions like network requests
- Promises represent data that will be available later
- Fetch API returns promises for network requests
- Database queries return promises
- Browser permissions like webcam access use promises
- Promises can resolve with data or reject on failure
- You can create promises manually or with async functions
- .then, .catch, .finally wait on promises
- async/await syntax is preferable for readability
Transcript
Wes Bos
Welcome to Syntax. Today, we've got a first of 3 shows on promise deep dive. So I was driving home from, dropping off my kids at school today, and I thought, like, what what should we what should we do? You know? And I have been keeping notes on just, like, all of these things that surround working with promises in JavaScript. And I realized, like, man, there's actually quite a bit to working with promises and architecting him in flow control and all that. So this is going to be the next 3 Mondays are going to be all hasty treats on deep dive into promises. So today, we've got sort of an introduction, understanding how promises work, and awaiting them in different ways to create and listen for a promise resolve. Then, 2nd day, we're gonna have 1 on canceling promises, helper methods, and error handling strategies. How do you how do you specifically deal with errors in a nice way for your application with promises? And then the last 1 we have is getting into concurrency, queuing, flow control, fetch, canceling them, and deferreds, which is a new API in in the browser. So buckle up for the next 3 weeks. We got all kinds of I promise
Scott Tolinski
you are going to enjoy it. I promise you. I was about to make that same joke.
Wes Bos
I Yes. Yes. Good. Oh, yeah. Let's see. How many promise jokes can we make in these next 3 weeks? And if you want to see all of the errors in your application,
Scott Tolinski
you'll want to check out Sanity at sentry.ioforward/ syntax. You can sign up today and get 2 months for free. Sentry is just a really incredible tool for not only tracking your performance, making sure your application has no bugs, but even just seeing what goes wrong when something goes wrong because things go wrong all the time when we're coding. And you don't want a production application out there that, well, you have no visibility into in case something is blowing up, and you might not even know it. So head on to reduce entry.ioforward/ syntax. Again, we've been using this tool for a long time, and it totally rules. Alright.
Scott Tolinski
I think this is a great idea, though, because, you know, I remember when I first learned about promises black back when they weren't in the browser, you had to use q or Bluebird or one of those. Right? Yeah. I remember being very confused about why they're a big Deno, what makes them interesting, how is it different than something like a callback function, What's the deal with promises? So I think this will be a really great episode. And in this episode, and specifically yeah. We're getting into promises 101, creating and waiting on promises. So I think we can start this off with the first question.
Promises help handle asynchronous actions like network requests
Wes Bos
What is a promise, Wes? A promise in JavaScript is an object that represents the eventual completion of some data, and that's different than a regular variable or a function return in JavaScript because, normally, the the result of a promise is returned immediately.
Wes Bos
You get something right away, but a promise is sort of an IOU, a promise that eventually, at some point in the future, I will give you some piece of data or I'm gonna fail, which is is called rejecting.
Promises represent data that will be available later
Wes Bos
So, again, a promise is a future agreement that some data will be returned to you. It's a commitment. Like, a promise. Commitment. Yeah.
Wes Bos
And I thought, like, let's just kick it off with just some common examples of what a promise would be. I know most people listening sort of understand what a promise would be, but let's let's talk about any type of data that can be returned from a promise, which is
Scott Tolinski
anything. The most common one being you wanna take this 1? Yeah. And I think the one that most people have used is fetch calls.
Fetch API returns promises for network requests
Scott Tolinski
If you go off and you know, there used to be libraries, and now we have a standardized fetch API.
Scott Tolinski
If you go off to grab some data from anywhere, and you're most likely using fetch. And if you've ever tried to access that data immediately, you'll know that it returns a promise.
Scott Tolinski
When you go off to do a fetch, it it doesn't know how long that's going to take. It says, I'm gonna go get some data. I'll be right back.
Scott Tolinski
And then and you say, alright. Let me wait for that data. Or when when the data does come back or perhaps you failed to get to the store, then you can, you know, bring that data back, and now we can do something with it. So you've gone off to a trip to the store. You've now come back with the goodies that you you ordered.
Wes Bos
Yes. And the promises being a feature of the language has been an amazing like, I know most languages have sort of the idea of a promise or or a way to do this type of work. But Mhmm.
Wes Bos
Being able to have it as a primitive in the language means that you can mix and match anything that returns a promise, and you can work with them together. This is one thing when there's a new thing called deferreds that have been added to promises.
Wes Bos
And it's not really, like, a new thing, but it's it's a new API that makes working with deferreds a little bit easier. And a lot of people are like, why are they adding all this garbage? Why don't you just go and use this library that already did it really well? And often, I'm in that camp. Like, Node. Maybe we don't need a very opinionated way to do this in the language, but with with promises being able to have a single type.
Wes Bos
Previously, it was like Scott said, you have to use Bluebird or a sync or callbacks.
Wes Bos
It was very hard to say, alright. Well, I'm gonna work with this database adapter, and I'm going to be working with this API that does validation or or does a bunch of queuing. And getting those to work together was always kind of frustrating. And now that we have we've had them in the the browser for for many, many years, it's much easier.
Wes Bos
Another example of promises would be a database query or an insert command, pretty much any database ORM that you're using these days.
Database queries return promises
Wes Bos
If your database is not gonna solve return immediately.
Wes Bos
And even if it did, that's something called a synchronous request or a blocking request. And what that would do is it would actually lock up the rest of your application from running. So if you it's very hard to actually write code these days where you create a blocking circumstance, but you could if you if you did have a blocking database right, you could run into the situation where your server's handling incoming requests from the user.
Wes Bos
And if you have a insert that takes 2 seconds, then all of a sudden you're blocking any requests that are coming in for 2 seconds, and that's that's sort of a bad day.
Wes Bos
So database queries, every ORM that you work with will return to a promise, and then you can sort of just wait for that to successfully give you the message. Say, I save the data or resolves to the query of the data that's been returned to you. Yep. If you if you notice a pattern here where, basically,
Scott Tolinski
anytime something could be blocking, we use a a promise. So that way, it takes something that could be blocking and makes it so it does not block the execution of other things, which is one of the reasons why we have to do the whole the whole then thing with promises that we'll get into.
Scott Tolinski
Another thing could be, requesting the user's permissions for things, like co webcam. Right? The reason why these have to be returned as promises because you're, again, you're waiting for something to come back from the user's computer, whether that is prompting them to do the permissions acceptance or whatever.
Browser permissions like webcam access use promises
Wes Bos
And then, a little wait function is a example of a promise that you might write yourself. So testing library where you wanna stop your Node from running, testing library Wes you wanna stop your code from running. You wait for something to show up on the page, and then when that thing is on the page, it will resolve. We'll talk about that in just a second, and you can continue on with your with your life. So promises have 2 ideas here is that when you when you create a promise, you get the option to resolve that promise or reject that promise.
Wes Bos
Resolving a promise is successfully returning the data that that promise has set out to to go get or to to go fetch for you or to you would resolve after somebody clicks a button. That's a successful outcome of a promise is called a resolve, and a negative outcome of a promise is called a reject. And often, you will call reject in your promises when you something goes awry. Right? That that database item didn't save correctly.
Promises can resolve with data or reject on failure
Wes Bos
The user did not actually give me access to their webcam.
Wes Bos
Something happened inside of this promise where there was an error that happened because of a different library.
Wes Bos
Those are reject cases.
Scott Tolinski
Yes. And I don't think this necessarily fits ESLint there, but there's also so you resolve or reject a promise, and and those things connect very firmly to Wes we're working on promises.
Scott Tolinski
We'll talk about this in a bit, but that those connect directly to the then. Right? The then method is called once a promise has been resolved. The catch method gets called when something is rejected, and then finally gets called no matter what. We'll then we'll talk about those more in a second. So, next step is creating promises. So how do you create your own promise? Oftentimes, we're consuming promises. Right? We're using a a dot then. We're using a async await. We're we're not necessarily writing them, but you can write your own promises using new promise. And you can create a promise, and that gives you a callback where you can use the resolve or reject methods to, like, Wes mentioned moments ago, either resolve and complete the promise or reject and fail the promise. Yeah. You are often creating your own promises
You can create promises manually or with async functions
Wes Bos
when you want to bundle together a bunch of functionality. And often you'll have, like, nested promises. So you might have a a promise called create user. Right? And in that create user promise or a function that returns a promise, I I promise I will go and create the user and return to you, the actual user once I'm done. But inside of that, you may be doing several things. Right? You may be doing some sort of validation. You might be checking if they have already been signed up. You might be hashing their password to store in a database. You will be actually saving that user to the database. You'll be creating time stamps. You'd be maybe creating some items that are related to that user, setting a bunch of permissions, and all of those things need to happen inside of your promise. They can also often chain them together. And then when you're successfully done, you resolve it. If there's an error anywhere along the way, you can reject that promise and say, this is this is explicitly what has happened.
Wes Bos
There's a new method, which I'll talk a bit more about in a future episode, this idea of deferreds.
Wes Bos
Mhmm. But Promise Scott with resolvers is another way to create a promise, and that will return to you the promise itself, but also the reject and resolve methods, at a top level. And this can be handy if you want to pass the resolver and reject methods to other parts of your application rather than doing your work inside of the promise itself.
Wes Bos
And then the 3rd way we have for creating a promise, and this is quite honestly the most common way these days, is you mark a function, a sync.
Wes Bos
And instead of creating a new promise and putting all of your code inside of a promise callback, which does have use cases, you simply just create a function. You mark it with the keyword async in front of it. Immediately, that function changes from a function that will return you data right away, turns into a function that will return a promise that will return data at some point. And the kind of the cool thing about async functions is that inside of an async function, you can simply just return a value.
Wes Bos
You can have other promises inside of there. And if you return either a value or a promise, you don't have to use the resolve method. You simply just return. And because the the function has been marked as a sync, it will have that it'll work just fine. The same thing with rejecting. You don't have to explicitly reject.
Wes Bos
You can throw an error, which you might be used to already. You Sanity, throw a new error. User email has already been registered.
Scott Tolinski
Yep. And here's a a small little thing for people who find themselves doing this.
Scott Tolinski
If your function returns a promise, what you don't wanna do is you don't wanna mark the function as async and then await that promise and then return the result.
Scott Tolinski
You just return the promise from that function, and you're returning a promise from the function rather than having to create a new promise and and do it that way. So like I mentioned, we're then gonna get into, like, the waiting on a promise bit of this because, again I got sorry. I got one more thing to say about that.
Wes Bos
You sometimes do have a sync functions that return promises, and and the use case for that is sometimes you want to first await something inside of that, and then you may want to return the actual promise itself from that function. There's no harm in, like, sort of double wrapping your promises.
Wes Bos
So if you were to have an async function that returns a promise itself, you're not gonna get into any weird, like, nested
Scott Tolinski
thens that that will be an issue for you. Do you think there is any sort of performance implications there? I I don't know if there is. I'm just wondering. I don't I don't think so. Often,
Wes Bos
I have tweeted screenshots of code that I've written, and I'll just, like, mark a function as a sync, and then I'll, like, I'll refactor it to return a promise.
Wes Bos
And sometimes there's an unnecessary a sink on there, which is why I like the ESLint rule that will be like, you didn't await anything inside of this. What are you doing marking it as a sync? But I don't think there is.
Wes Bos
At least I don't know of of any, like, performance downside to, having a function marked as a sync if you are returning a promise.
Scott Tolinski
Word.
Scott Tolinski
Alright. Well, let's talk about waiting on a promise then because like we mentioned, a promise is you know? Yeah. It's a commitment to come back and and you know? I'm promising to to go to the store and get some candy cigarettes. When I come back, I better have those candy cigarettes. Do you remember those, by the way? Did you ever get those in Canada? Yeah. It's it's pretty wild that we just, like, had cigarettes, and they're, like, red on the tips. Like, clearly, you're trying to make these fake cigarettes. But I said you're supposed to, like, keep keep them in your mouth. I just chomped them up. That's what I did. They're, like, chomping. I can't. Yeah. I can't do that.
.then, .catch, .finally wait on promises
Scott Tolinski
Alright. Well, let's talk about waiting on those promises. Right? So the main way that a lot of people have done it is with a dot then method.
Scott Tolinski
So instead of using a sync await or anything like that, you have your promise, then you say you have a function call, right, obviously, and then you chain a Scott then to the end of that.
Scott Tolinski
Dot then has a callback, and then that callback has the data that's available to you if that promise is resolved correctly.
Scott Tolinski
If the promise rejects, you would then need to also chain a dot catch.
Scott Tolinski
I don't know why. I I Wes I don't know why I said that, you know, apprehensively.
Scott Tolinski
You then need to chain a a dot catch in which the error will be available in the callback function. And then there's also, a third one that a lot of people don't use, which is finally, which this will go no matter what. So if you find yourself duplicating some logic, maybe, like, you're waiting for a value and then you're setting a state toggle based on that value, if that state toggle is toggled Deno matter what, if there's an error or a fit a success state, you could put that in your finally. So the finally is gonna run no matter what after the catch and after the then.
Wes Bos
Yeah. That's that's really handy if you, like, have, like, a loading state and you need to turn the loading state off in both cases. Right? And after a successful return or, on error, I wanna turn the loading state off.
Wes Bos
The the finally is super handy for that. And, quite honestly, I don't see it used all that often. So I think I should maybe start using a little bit more myself.
Wes Bos
So the dot then is a pretty common way to do it, and then we also have the ability to await promises. So when you're in a function that is marked as a sync, you can sort of pause that function from running.
async/await syntax is preferable for readability
Wes Bos
And once the promise resolves itself, you can continue ongoing. So it's a beautiful API because you can simply just say, like, const user equals await create user. Whereas the create user function would return a promise, putting in a weight in front of it will will sort of unwrap the promise. It will wait for the promise to resolve and give you the actual data at the end of the day. A weight also is used for generator functions, but we're not getting into that in the promise episode.
Wes Bos
So I in most cases, I would say most people use the async await API simply because it reads a lot nicer.
Wes Bos
We'll talk a little bit more about error handling and and how to mix and match them. There are use cases where you still want to use the dot then. Chaining is is a really nice one instead of just doing await await await. Some people like to chain then then then onto one another.
Wes Bos
The other one being that sometimes you want to listen for Wes something is resolved, but keep the function going while you're waiting for it. So a very common use case that I've seen is you want to go off and fetch something.
Wes Bos
And then the next line below that, you wanna set up, like, an event handler for when somebody clicks something or when a specific event happens. If you were to await that fetch request, the event handler would never run until because you're essentially pausing the use case. Same thing with streams as well. That's pretty common use case Wes, all right, I'm going to set up this stream and I'm gonna resolve once it's actually finished. So what what do you use, Scott, in in most cases? Do you default to sync await?
Scott Tolinski
Yeah. My default is it is funny because it sometimes it feels haphazard in which I I'll be like, oh, I'm not inside of an async function. I don't feel like making this function async, so I'll just throw a dot then on there.
Scott Tolinski
I to me, it depends on, like, what I have to do with that data. The thing that's always bugged me about the then methods is that you're now going another layer deep. Right? If you wanna access that data outside of the promise, you have to create a variable and pass it and worry about the default values before and after the the promise return. So almost always, I'm using await just because it reads more like how I would expect it to work, and then I can access the values where I want to access them. Alright. Wait for the value. It's there. Now I can do something with it. But, you know, there certainly are times where I am using a a catch or a dot then as well. I do use a dot catch even on await instead of doing a try catch, though.
Wes Bos
Yeah. That's, that's something we have coming up in the next one, which is error handling strategies of, like, mix and matching.
Wes Bos
I personally do that as well as I await, but I'd use a dot catch to to catch the error in in most cases.
Wes Bos
The chaining the dot thens onto each other, and I should say the way that this works is if you have a dot then, if you return another promise from that dot then, you can chain them indefinitely and put a single dot catch at the end of it, any errors that happen along the chain. Where that becomes an issue is then you have your a weird scope, which is hard, and it's also really hard to, like, pass data between the 2 of them. So if you have 1 promise that fetches the weather and then another promise that saves the weather to the database, you find yourself having to modify functions so that they'll accept the incoming weather data. And then that's really frustrating because now if you wanna comment Node of them out, then you have to change the signatures again, or you create, like, a a variable that's Node the scope of the dot then and you sort of update it. That's annoying because now you have like an undefined variable for a short amount of time and you have TypeScript. That's a bit tricky.
Wes Bos
So yeah, I like I like the the I think await much better. The only time like I'll mix and match them is often with the fetch.
Wes Bos
I'll have the await whatever.
Wes Bos
And if you have to get JSON back, you know it's JSON.
Wes Bos
The fetch has, like, a double promise thing, which we'll explain in the in the next episode. Sometimes I'll just chain a little dot then on top of it.
Wes Bos
Word. But I think that's that's all we have for the 1st episode. Again, that was pretty high level of explaining promises. In the next episode, we're gonna get into canceling promises, aborting promises, some of the promise helpers, which is all all settled, finally, any, and race, as well as 5 different ways to handle error handling strategies.
Scott Tolinski
Sick.
Scott Tolinski
I look forward to your promise of future episodes. Promise it will be good. Peace. I can't
Wes Bos
wait.