Check out a free preview of the full Rethinking Asynchronous JavaScript course

The "Abstractions" Lesson is part of the full, Rethinking Asynchronous JavaScript course featured in this preview video. Here's what you'd learn in this lesson:

Kyle introduces a few abstractions in the promise API. Promise.all() takes in an array of promises and will wait for all promises in the array to resolve. Promise.race() also receives an array of promises, however, it only waits for whichever promise in the array resolves or rejects first.


Transcript from the "Abstractions" Lesson

>> [MUSIC]

>> Kyle: Promises, pretty good at their core. But there's an awful lot of Abstractions that were missing. And Abstractions are what are one of the powerful tools that we use to make higher and higher order patterns that let us be more and more expressive with our code. I want to talk to you about a few Abstractions is the first couple are things that are built in directly into the promise API in the browser.

Then we'll talk about other Abstractions that are available to us through third party and libraries that wrap and build on top of promises with other Abstractions.. The first one we can talk about is Promise.all. Promise,all takes an array of promises. As you can see, you have an array of promises that are coming from these doTask methods, doTask1a, 1b and 1c.

Now what it's going to do is obviously all of those functions are being call which means they're all producing promises. We can imagine that that's like three independent AJAX calls happening all at once. And then we're going to wait for all of those to finish and you'll notice that we get an array of those results, passed into us on line 6.

There array of results, will have the array of results from each of those promises in the order they were listed in the original array. Regardless of what order they finish in, they're in a predictable order which is request order. This is allowing us to say, I don't want to just wait for one promise.

I've got three that need to happen in parallel. And then I need to wait for all of them to finish before moving on. You notice the Promise.all is giving us a promise which were then [INAUDIBLE] off of on line 6. And what we're getting as the completion result from that previous promise, is an array of all the results and here I'm just a silly illustration I'm doing Math.max across those three results.

We might have been task 1a, 1b and 1c might have been to generate some random number. We're gonna ask for the maximum of the three numbers that was generated and then return and pass that in to doTask2 and that returns a promise which we can of course change.

The point that we're making here, is that each step in a promise chain can be as simple as, chaining in another promise, can be as simple as just an immediate value that we return or it could be something more complex, a higher order abstraction like a that we see here.

Old school computer science terminology we use for this we call this a gate. Whenever you have multiple things happening, you do not know what order they're gonna finish in, but all of them need to finish before moving on, we call that a gate. The API here is Promise.all but you can think of that as a gate.

Now if any one of those three promises creates a rejection, the main problem the one that we're chaining off of on line six, that main promises immediately rejected. So .all requires successful completion. You could think of an abstraction where I don't care about successful completion, I just need all of them to finish either positively or negatively.

And that would be a different abstraction. You can start to think there is actually quite a few different abstractions that we can invent. You might invent an abstraction like I want to be notified when the first one finishes or when the first one finishes or fails. And that abstraction, the one I just mention, which is I want to be notified whenever somebody finishes or fails, that's called a race.

We also have a utility called Promise.race, takes an array of promises. And its definition is to say, I'm going to wait for any resolution, whether that's a fulfillment or a rejection, any resolution of any of these promises, whichever one crosses the finish line first wins, and everybody else gets ignored.

That's what Promise.race is about. Here I'm showing a timeout idiom, because we didn't answer earlier. I kind of glossed over it, hoping that you would either ask or at least remember at this point. We didn't ask, well, what happens if my promise never gets resolved? The way you handle that is the way we handle any asynchronous task that may never complete, which is to set up some maximum timeout for it.

Here, what I'm doing is I've got p on line 4 representing the promise that I care about. And then I'm creating another promise on line 5, which I don't really care about, it's just set up as a time bomb, that after three seconds, it rejects. One of two things is going to occur in this scenario.

Either my promise is going to finish in less than three seconds, and I'm going to get its result, and move on to line 11, with a success on line 12, or the rejection promise is going to finish first, in which case the other promise, whether it ever finishes or not, we're going to ignore it, and we're going to move to line 13, because this was a promise rejection.

We're going to handle a time out error.
>> Speaker 2: If the time out happens, what happens to the original promise, does it get garbage collected, or does it-
>> Kyle: Yes.
>> Speaker 2: Stay around? It would potentially get flushed.
>> Kyle: Yep.
>> Speaker 2: Okay, cuz there's no reference to it anymore.
>> Kyle: Yep, yep.

Promise.race throws away anything, once it finishes, it throws away all its references.
>> Kyle: [COUGH] Now this is not a terribly graceful piece of code to write every time you wanna time out a promise. That would be nice if there was some higher order obstruction for timing out promises, and it turns out there is.

Virtually every promise library out there has something for timing out promises, and out of the covers they're doing something roughly like what I'm talking about or setting up a race basically
>> Kyle: Promise.all and Promise.race shift natively with the API that comes in the browser. But we can already start, and some of your brains may already start to be thinking, what about this abstraction, what about this abstraction?

What about the one where it's like I only care about the first one to successfully cross the finish line. We might call instead of calling that a race we might call that first. There's an abstraction for that and libraries provide those abstractions. What if I only care about the last one, last one wins or we could call .last.

What if I want to know that all of them rejected. It's the inverse of Promise.all, instead of all of them succeeding, I want to know of all of them reject. We could call that .none so we could start to invent dozens of different abstractions that can help us and these abstractions become Lego building blocks that we build our more complex flow control center.

Every single step in our chain can be one of these abstracted steps instead of just a single, if then sort of abstraction. You want to look for those common abstractions and use them just like in functional programming you wanna look for things like map and reduce and develop an instinct for not reinventing the wheel, but using the wheel that's already been invented and knowing how it works.

You want to develop that same instinct around promise abstractions. Rather than trying to invent your own patterns, look for ones that have already been established and well worn.
>> Kyle: All right, more reading about promises, I promised, I promised you, pun unintended. I promised you earlier that I had written a bunch about this, this link at the top link is the first in a five part blog post series that I wrote.

It's pretty long, all about promises. And I talk about error handling and all kinds of other things. I talk about cancelable promises. It's all in there. If you wanna read about promises in depth, that's a blog post series. I've written about promises extensively in the book series, both in the Async and performance book, as well as the ES6 books, so there's plenty to read there about them.

And if you want to use promises in straight up native form in a polyfill fashion that is to use them in older browsers but use them where there's no additions or anything. I wrote a polyfill for promises called native-promise-only. There are about a dozen or so of this polyfills out there.

One word of caution on looking for promise polyfills. Something that kind of bothers me is that there's quite a few libraries out there which claim to be promise polyfills, really what they are promise libraries, that add a bunch of extra stuff on top of the spec, add a bunch of extra helpers in.

Am I against adding helpers? Absolutely not. I'm about to show you a library that I wrote that adds a bunch of helpers. I'm a big fan of library helper's because there's a lot of stuff we want to do with promises. Well I'm not a fan of his adding a bunch of helpers and then claiming to be a polyfill, because a polyfill is supposed to be nothing but the specification compliance.

No cutting corners, no adding extra stuff and there's a bunch of libraries out there that sort of cut some corners and add some extra things in, and still call themselves polyfills. Be cautious about looking for that. You can trust that native promise only does nothing but implement the spec compliant version of promise.

Learn Straight from the Experts Who Shape the Modern Web

  • In-depth Courses
  • Industry Leading Experts
  • Learning Paths
  • Live Interactive Workshops
Get Unlimited Access Now