May 20th, 2024 × #promises#error-handling#javascript
Promises: Error Handling, Aborts, and Helper Methods - Part 2
Discussion on advanced promise concepts like canceling, controlling, helper methods, error handling strategies, and static methods on the Promise constructor.
- Promises for good understanding of promises
- Canceling promises
- Controlling promises
- Promise resolvers
- When to use abort signals
- Example of abort signals for search
- Promise all vs promise all settled
- Promise helper methods
- Error handling in promises
- Strategies for async/await error handling
- Promise static methods
Transcript
Scott Tolinski
Oh, welcome to Syntax. In this episode, we're gonna be taking a part 2 to our series on promises. And in this video, we're gonna be talking about air handling. We're gonna be talking about aborting a promise, helper methods, and more. So, yeah, we're gonna continue our our talk on promises. And after this one, I think you'll have a a great idea about a little bit more in terms of how to use them beyond just the basics. So my name is Scott Tolinski.
Wes Bos
I promise you're gonna have a good idea. Yeah. Okay.
Promises for good understanding of promises
Scott Tolinski
Well, I promise you, my name is Scott Talinski, and I'm from Denver. With me, as always, is Wes Bos. What's up, Wes? Hey. Not too much. I promise
Wes Bos
that if you have issues with your promises, you're gonna wanna check out Century because Century is going to tell you when your promises are thrown, maybe when they're you have an uncaught reject in one of your promises that you weren't expecting.
Wes Bos
I can tell you, I, myself, Node used to have this thing called Node used to catch your uncaught promises, and they warned for years that they're gonna turn it off. And I said, yeah. Yeah.
Wes Bos
And then Node day, my servers my server crashed.
Wes Bos
And whenever my server crashes, I get a little Sanity alert, and I get an email about it. And I logged in. I thought, why did my server crash? And I looked at the century error.
Wes Bos
It was a fetch request that I had not been or Node. It wasn't a yeah. It was a fetch request, but it was a post. So I was posting some data to a third party service. That service had an outage.
Wes Bos
And because it was unhandled, I didn't catch the error. Yarn. My entire server crashed, which is unreal that, it could crash an entire server just with a a single, fetch Wes. But that happens, and Sanity was able to tell me exactly what happened, and I fixed it in, like, 3 minutes. So check it out, Sanity Scott I o. Check it out.
Scott Tolinski
Sick.
Canceling promises
Scott Tolinski
Well, let's get into the 1st section of this episode. We're gonna be talking about canceling promises. So you have a promise. It goes off and, like, I I'm sending, or you're sending me to the store. Right? The promise is that I'll return with the stuff.
Scott Tolinski
At any given point, you can call me up and say, hey, Scott. Come back.
Scott Tolinski
Yeah. Cut it out. Stop doing what you're doing. I found the cheese in the fridge. We don't need any more cheese. Or, it's a good way. You can yeah. You could reject a promise at any time.
Scott Tolinski
So that is entirely up to you.
Scott Tolinski
And you can return with the resolve or reject methods at any time as well.
Controlling promises
Scott Tolinski
You basically have control over the full process throughout which you're working with your promise.
Wes Bos
Yeah. Inside of a promise, you can like we said in the last episode, you get your resolve, you get your reject methods, and you can do whatever you want with those. And often, what that in includes is you have some logic inside of your promise that says, given these use cases, maybe after I've tried clicking the button 6 times and it doesn't work, then then reject or after. Pretty common is you want to put a 5 second time out on a promise after 5 seconds. If this thing doesn't come back with data or after 5 seconds, if the user hasn't clicked this button, then we need to do something in order to to show them. So you'll catch the reject in that in that case. So that that's an example of when you would want to put the logic inside of a promise.
Promise resolvers
Wes Bos
Also, we have promised that with resolvers.
Wes Bos
Again, we'll talk about them a little bit more in the next episode. It's called deferred, but, essentially, promise that with resolvers will give you the promise, but it will also give you the resolve and the reject methods outside of the promise instead of inside of the promise callback.
Wes Bos
And that can be handy JS if you want to pass those, like, pretty much, like, succeed and Vercel, functions if you wanna pass them to something else. Like, for example, I have buttons on an example somewhere where if you press, like, the done button, that is resolved. But if you push the cancel button, that is a reject. Often, you could just wire those up directly to an ad event listener, and you're up and running. So that's another way you can cancel them. And then also in fetch land, Wes have this idea called an abort controller.
Wes Bos
And you create an abort controller, and that abort controller itself has what's called a signal. You pass that signal to a fetch request, and then you're able to control that fetch request from wherever you have access to the abort controller method. It's a little bit more complex than simply just rejecting because you do want to also cancel the network Wes, and that will take care of all of that stuff for you JS well as there's a built in signal for simply just doing a time out. Because if you wanna set the time out of a fetch function, you don't have to wrap it or anything. You simply just pass the signal of abort signal Scott time out, 500 milliseconds, and then that thing will reject if there's no response back after 5 seconds.
When to use abort signals
Scott Tolinski
Why would you reach for an abort signal? Because that's not something I feel like I've ever had to do.
Wes Bos
A good example would be if you have a a type ahead that is sending off fetch requests. So you have a search Bos, you start typing in cool, and then you stop for a second, and it goes, I'm gonna start searching for cool. So it sends a fetch request off to your API.
Wes Bos
And as that fetch request is in flight, you just type I f y f I.
Wes Bos
Okay. Now you've you've sent off a second request to the server that for Coolify. Now you have two fetch requests in flight and it is not guaranteed that they will come back in order that you've done it. And often developers don't realize this because you're working with a local database and on local hosts, and there's no latency at all. But you get into real world conditions.
Wes Bos
Your database is somewhere different than your actual user. There could be real world things. So as you are typing, if you are firing off a second fetch request, you need to make sure that you abort any other requests that are currently still in flight. So that's where you'd wanna use an abort signal.
Scott Tolinski
Thank you. Thank you for that. Yeah. Yeah. Well, let's talk about some of these additional helpers for promises. You may have seen some of these, like, promise Scott all, which promise dot all accepts an array of promises.
Example of abort signals for search
Scott Tolinski
And what it will do is it returns or why I should say the promise itself fulfills once the promises themselves complete.
Scott Tolinski
And it usually gives you essentially an array of the values that come back from those promises.
Scott Tolinski
Now I am curious, Wes, because I know you know more about this than I do.
Scott Tolinski
Promise Scott all settled. What is the difference between promise all and promise all settled?
Wes Bos
So all was initially rolled out when we got promises.
Promise all vs promise all settled
Wes Bos
And the downside to that is if one of those values rejects, the whole thing is is over. So promise that all takes, like you said, an array of promises.
Wes Bos
It wraps them up into 1 mega promise. Yeah. And it waits for all of the promises to be done. And if one of them takes 1 second and one of them takes 10 seconds, you're gonna be waiting the full 10 seconds before you get all of the data. Right? But if one of those were to reject, you might have 2 successful promises, but there's no way to get that data anymore because it it's rejected, and immediately you go straight to the catch when you're you're chaining. Right? So that's kind of a pain if you do care about the successful ones, but not the one that was was failed. Right? Like, for example, if you're uploading 7 photos, you might want to do promise that all and upload all 7 concurrently all at the same time. However, if 1 of them doesn't upload because it's too large or it's the wrong file type or something like that, The other 6 that may have been uploaded correctly or are are still in process, they're all aborted. Right? You're you're you're out of luck. So all settled will return to you an array of the results regardless of if they were all successful or not. They'll give you an array of 2 types of data. Each item in the array will be will have a status, which is fulfilled or rejected, which is a little annoyed to me that that's it's called fulfilled or rejected. Shouldn't that say resolved or rejected? Maybe not. And then on successful ones, you'll get a value property, which has the data that you're looking for. And then the errored out ones, you'll get a reason property, which will have whatever is passed to the rejected method that you have there. So I would say I don't know. Unless you I wanna abort everything, abandon all ship as soon as one of them breaks, you probably want all settled in most cases. Yeah. And then from that, finally, you mentioned this last show of it being really handy JS if you want to do something after it resolves or rejects, like turn off a loader, then you can use finally, and that will run-in either case.
Wes Bos
Now there is also I can confidently say I've never used either of these. Yes. I'm curious if you ever have either. There's promise dot any and promise dot race.
Promise helper methods
Wes Bos
Do you wanna guess what those are for unless you've used them yourself? Do you know what the difference is? I you know what? Just looking at these, I would say
Scott Tolinski
that any resolves once any of the promises have resolved.
Scott Tolinski
Mhmm.
Scott Tolinski
But maybe okay. Here's what I'm gonna guess. This is this is just totally off the wall guess for me. I'm gonna say they both return once the 1st promise resolves. I would imagine that maybe any also resolves the other promises where race stops trying to resolve the other promises?
Wes Bos
It's close. So they both you're right in that. They both will resolve as soon as the 1st promise is is uploaded. So maybe you have 2 different APIs, and you're sending data to 2 because you you wanna have, like, double backups. Right? And you only care that something has been sent to one of them. Right? And both of those will resolve as soon as the first one is finished.
Wes Bos
The difference being that promise Scott race will return the 1st settled value, and a settled is a reject or a resolve, whereas promised that any will return the 1st fulfilled value. So any is success only.
Wes Bos
Race will return the rejected or resolved
Scott Tolinski
value. The first Node. Again, I don't know. Like,
Wes Bos
in in what case are you firing off multiple promises? Maybe, like, if you had a promise that was waiting for a click on multiple buttons. Mhmm. So you might have, like, 6 promises that are waiting for clicks on 6 different buttons.
Wes Bos
You might only care as soon as somebody clicks one of those buttons.
Wes Bos
And as soon as somebody clicks one of those buttons, then you wanna continue on with the rest. So although, like, just you could pass in multiple selectors to something like that as well. I don't know. That's a contrived example that
Scott Tolinski
I'm trying to trying to twist into work in here. Well, if you're out there and you're listening to this and saying, guys, I have the perfect use case for this.
Scott Tolinski
Hit us up on YouTube. Drop a comment in the video below this video. I wanna hear what people are using race for specifically, because I have used any, but I've never used race before. And either way, I these aren't things I need to reach for very often.
Error handling in promises
Scott Tolinski
Let's talk about error handling within promises. Now typically, if you're chaining methods on a promise, you can always add a dot catch method that takes a callback with the error that gets returned there. So you've seen that Wes it has a callback with an e or an error, then you can console log the error. You can throw it to Sanity.
Scott Tolinski
You can do all kinds of stuff with it to ensure that your UI JS accordingly to whatever is happening with that dot catch. Now you can also use a try catch statement to wrap your await inside of Wes you're going to await a promise.
Scott Tolinski
However, one thing that I learned from Wes a little while ago JS that you can mid mix these approaches where you can await a variable result, but still chain a dot catch onto that bad boy. So that way, you don't have to wrap the whole thing inside of a try catch. I don't know why, but try catch feels so obnoxious to me. It pnpm you like, it then reindense my code all by 1. It feels like it takes up a lot of space. That's one of those ones where it's like, I will use the Scott catch method, and I will use await to get the value out of a promise, typically.
Wes Bos
One kind of way that I've been writing a lot of my promises lately is this idea of writing a wrapper function around your promises that will internally run a try catch and will return return from that function a what's called a tuple. And a a tuple is like an array that has a known length and a known type.
Strategies for async/await error handling
Wes Bos
And back in the day, in express JS and Deno JS land, the way that it worked is your callbacks would often give you the error and the data, and you would either have the error or the data. And that was really nice because you could first check if there was an error, and if there's not, you can continue on with the actual data.
Wes Bos
And that's a little bit tricky because you could do try catch, and then you gotta update variables outside of the scope. That's kind of annoying. You can use the away and dot catch, but if you want the actual error on the next line, then, again, it's it's out of scope. You don't have access to it. You gotta do some weird variable things.
Wes Bos
So with this idea of I call it collect.
Wes Bos
I in my TypeScript course, we create a function called collect and learn how to, like, use generics to type something that is so abstract.
Wes Bos
But there's a really popular library out there called await to JS, and this will just give you a function that you can wrap your promises in. And then it will return to you an array. 1st item being the actual error, 2nd item being the actual data. And that's really nice if you need to first deal with an error before you go on with the rest of your thing. Most commonly being, like, you you try to save an item to a database.
Wes Bos
You get the result back. You wanna first check. Wes there any errors saving that item to the database? If if so, then render an error page. If not, continue on with the rest of the page, maybe render out the user page.
Scott Tolinski
So I've been a big fan of this approach. I have 22 statements on this. 1 Yes. We we should take your collect and turn it into a library, and we can call it collect call. That's good. That's a good joke. Oh. Yeah. That's great. Thank you. Yep. And, second, I you know, just even saying this makes me wonder why, you know, dot catch or even like, it feels like the error should be the first in my logical brain when you're doing a promise. Like, oh, if this fails, take care of the failure first.
Scott Tolinski
Then if it didn't fail, we know it succeeds. Like, logically, the order of that makes way too much sense. I've never used await to JS or any of this stuff before, but I like the way it reads when you look at the code.
Wes Bos
Yeah. I was I always was a big fan of this approach. I think it was I'm trying to wonder, like, who popularized this in Node. Js land? I think some of the even the Node APIs that are callback based were like that.
Wes Bos
It's been so long since I've worked on it, but, yeah, it was always give you the error first, then the data, and that forces you to think, okay.
Wes Bos
Handle the bad case first, and then you have your your happy path. Gotta have your happy path. So, yeah, that's I have a little YouTube video I'll link up here detailing the different async await error handling strategies because not one is better than the other. I use all of them. Sometimes I try catch. Sometimes I catch is good. Often, a mix and match is really good. And then if I'm doing specifically, when I do a lot of, like, Node. Js database work, I'll reach for the collect or await to JS implementation.
Scott Tolinski
Yeah. Yeah. It's it's it is interesting because you like you said, like, you do it different ways. To me, it's one of those as a feeling things because you can typically write most of the stuff you're gonna do with any of these approaches.
Scott Tolinski
And I think over time, you just kind of get feeling for which way you like to do what, and that's that's typically how it feels for me.
Wes Bos
I have a really good promise dot race example. So often when I don't know how people use APIs, I'll just go and get up search and search for that API and see. I'll just add to scroll through 15, 20 different code examples.
Wes Bos
Like, what are people actually using this for? And you know what? This is such a good example of of what it's used for JS that if you want to add a time out to something that has a promise, but that promise doesn't have the option to pass it in. Like, you're using somebody else's API.
Wes Bos
They don't allow you to to pass in a time out. Right? Or you have your own promise based function, but then you have to reimplement the time out functionality in every function.
Wes Bos
And, you know, you have to add a timer to it, and that's annoying. That's one of the nice things about fetch where it's built in, but not everything is a fetch. So promise dot race, what you can do is you can say, const result equals await promise dot race, pass it your original promise function, and pass it a like, a time out function that will throw.
Wes Bos
And then that's beautiful because It makes sense. Either or or not even throw. You could just resolve after one second. And then if if the result is empty, then it it timed out. And if the result is there, then then it worked.
Scott Tolinski
Love that. Hey. Love that.
Wes Bos
Yeah. Oh, I learned a thing or two. Last thing we have here is the capital p promise in JavaScript has 2 static methods on it. So Scott reject and dot resolve.
Wes Bos
And I've always wondered, like, why are those there? You know? Like, what are those 4? And I I realized I had used them a couple times JS if you are returning values from a function that may not be a promise, but you want to still maintain the whole promise API because everything else you're working with is a promise.
Promise static methods
Wes Bos
So for example, you might have a cache API that either fetches some data and returns to you or it would just pull it up from the cache and return it to you. So by returning promise dot resolve with some data, it just turns your static data. Like, promise Scott resolve 42, it turns it into a promise that immediately resolves to 42.
Wes Bos
And that's great if you're trying to, like, keep the chaining or you're you're you're passing something to a function that expects a promise and not just a straight up value.
Wes Bos
So I think I think await kind of did away with the need for most of this stuff because if you have an async function that returns 42, that's the same thing. But if you need to turn a function that returns a value into a function that returns a promise of a value, that's where you use reject and resolve methods, the static ones on capital p promise.
Wes Bos
That's where do we at here? 23 minutes? Yeah. That's enough for a part 2. Hopefully, you learn a thing or 2. On pnpm Monday, we have the 3rd installment of the series coming out Wes we're gonna talk a little bit more about queuing and concurrency and running Yeah. Promises in series and whole bunch of libraries that are helpful for working with this type of stuff.
Wes Bos
Tech.
Wes Bos
Alright. Talk to you later. Peace.