Rethinking Asynchronous JavaScript

Promises + Generators

Kyle Simpson

Kyle Simpson

You Don't Know JS
Rethinking Asynchronous JavaScript

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

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

While generators completely solve the issue of sequencing asynchronous operations, they are still susceptible to inversion of control. Since promises solve the inversion of control issue, Kyle explains how they should be combined with generators to create better asynchronous code.


Transcript from the "Promises + Generators" Lesson

>> [MUSIC]

>> Kyle: Now you notice that when I call the getData function, it has no return value. So I'm technically yielding out undefined. That doesn't cause a problem here because we have the getData wired specifically to call But there's an implementation change that we can make, which is gonna reconfigure the way all of this stuff is happening.

From the inside, it's gonna look exactly the same, but what we can observe here, which is actually kind of good that some of these questions were being asked from the chat. Because what we can observe here is we're still every bit as susceptible to callback hell as we always were, aren't we?

At least one of the things of callback hell. We have an inversion of control issue, don't we? Somebody can call in a way that we're not expecting and screw everything up. We still have an inversion of control issue. We've solved the non-local, non-sequential reasonability issue. But we have no solution currently in this code base for the inversion of control callback hell issue.

So guess what we're gonna do? We're gonna put promises together with generators. Promises are gonna solve our inversion of control issue. Solve our trust issue. Solve our flow control control issue. Generators are gonna solve our non-local, non-sequential reasonability issue. And the key recognition for this, instead of yielding an undefined value, we're going to wire this up so that we are going to yield a promise.

Now I want you to think about this conceptually before I show you any code, look at me. Inside of my code when I say yield and I make an Ajax call, if that Ajax call returns me a promise for it, am I yielding out its promise? Yes, so who's gonna get the promise that I just yielded out?

>> Kyle: Whoever is driving my generator with its call, right? So imagine there's some plumbing somewhere which is gonna be in a framework or a library. But there's some plumbing somewhere that says, and what it gets back is a promise. So what's it gonna do with that promise?

It's gonna say promise.then So here's how this works. We yield out a promise, pausing and then we say .then and when the promise resolves, we resume the generator. And then we yield and pause and then and resume and yield and pause and then resume. That's how we make that magic work.

That's how we wire promises into the generator. We yield out promises and then the promises resume the generator and when the promise has some value to pass along, it passes it along to the call. So that plumbing, that circular plumbing that I'm talking about. You could write a utility to do that for your generators.

It's about 25, 30 lines of code to handle all the little nuances in error handling and all that other stuff. You could do it. It's in my book, you can see the snippet right there if you want to write your own. But you shouldn't have to. I want you to understand the concept of what it's doing.

It's yielding out a promise and resuming a generator and yielding out a promise. I want you to understand that concept. But it's not practical for each of you to go invent your own driver for these generators. So you're gonna need one that's built into a library and it turns out asynquence has one, Q has one, co has one.

I mean, all the major libraries, they all have this, cuz they all know that this is the most important pattern to land in JavaScript programming in a really long time. So they all have these drivers, mine's called runner. As you see on line 8, he passed the generator to the runner method.

And it automatically knows that if you yield out a sequence or yield out a promise, it automatically knows to wait for that and then resume and then wait for it and then resume.
>> Kyle: The main point of this slide is to illustrate that the value of asynquence is that a generator driving, like we're talking about, is really just a step in a sequence.

So I can put it right in the middle of another sequence if I really needed to. That's why on line 16 I have another step in my sequence that only fires after the generator is fully completed. So once you wrap your head around the abstraction that asynquence provides, the sequence abstraction.

Every single step in your sequence becomes whatever you want it to be. Whether it's a generator run, or a promise, or a reactive sequence or whatever.
>> Kyle: This is why generators are so powerful. Yeah, should have a few of those, [SOUND] what? This is why generators are so powerful cuz they completely change the game for how we write our asynchronous code.

Remember what I said at the very beginning of our workshop last time we were together? I said we're not talking about introducing new functionality, we're talking about introducing new expressivity, why? Because bugs happen when your brain doesn't work like the code that you're writing. So we're trying to come up with a pattern for writing code that works more closely like our brains work.

Do you see why this is so much better than the callback hell that we've talked about or even the promise chains or the thunk nesting or whatever? This is so much better because it works the way our brains work. And because we weave in promises, we get all the trustability the lack of inversion of control, all that stuff's fixed.

So we put those two together and we get, finally, a solution a callback hell.
>> Kyle: Now, this pattern is so important that they've already advanced a proposal that's a virtual certainty. They've advanced a proposal for the next version of JavaScript to create another new type of function, called an async function.

That does exactly what I just showed you, but without any library needing to drive it. Here's what it's gonna look like.
>> Kyle: Instead of having something like ASQ().runner, and passing in your generator, and then doing a bunch of yield ajax(..) calls like that. That's what works now cuz you have a library that will do all the plumbing for you.

This new proposal says, take out the library, just make an asynchronous function,
>> Kyle: Like call it foo, for example. And just call foo. And now we use await instead of yield. Anybody in here have experience with C#? C# programmers, if you're listening in, you'll recognize the async await.

This is exactly async await from C#. We're codifying it into JavaScript. So it's exactly that pattern that I talked about, about yielding a promise out, and resuming the generator. That old conceptual model that we just talked about. But now, we don't even need a library, we can just express it directly with syntax.

>> Kyle: I'm 100% convinced, this is the new baseline for understanding how to write asynchronous code in JavaScript. This is where we all have to get to. I said the last time we were together. You don't get anything else out of the workshop, you gotta get to this point cuz it's the new baseline.

It's the new level of competency that we all have to wrap our brains around and is it gonna come like that overnight? No, it's gonna take some practice and some work. But it's just so much better, it's so game changing compared to what we've been doing all along.

But the value, I hope the value is evident, the value should be clearly worth it.
>> Kyle: Yes?
>> Speaker 2: They're asking about the quality and speed of generators in transpiled code,
>> Kyle: [LAUGH]
>> Speaker 2: Is it really slow?
>> Kyle: [LAUGH] Transpiled code is not great code. But transpiled code is no worse and often better than all the really crappy code that you've been writing for 20 years.

So is the transpiled code good? It's not as good as ES6, but it's probably at least as good, if not much better, than the stuff that you would have written yourself.
>> Speaker 3: Is there a polyfill for async in await?
>> Kyle: These are syntax, so they're not polyfill, they're transpiled.

Babel does support ES7 async await.
>> Kyle: Just as a quick side note. I like the async await syntax. I like the non-library version of this. Because any time we can do something natively without a library. Yay, that's awesome. But there are some caveats that you should be aware of with async functions that are not true of generators.

So it's not just syntactic sugar. One of the major things, you member we talked about cancelable promises earlier, and I said I'm not a big fan of them? Here's where they come from because we're gonna make async functions, foo, when we call an async function, it's gonna return us a promise that lets us know when that function finishes.

An async function produces a promise that gets resolved when the async function finishes, right? But here's the problem, what if that async function is running and then some other external influence says I need to abort. I need to cancel whatever it's doing. Maybe the asynch function is making some really resource intensive long running Ajax call and downloading lots of records and something else happens in your application and you don't want that to happen any more.

From the outside world, how do you cancel a running async function?
>> Kyle: The answer is you can't. There is no cancel method on a running async function. So you know what they want to do? They wanna make the promise that comes back from an async function, a cancellable promise so that you can cancel the promise.

And that will send the cancellation message magically into the async function. This is an unbelievably, ridiculously, stupid idea, in my opinion. They ought to have async functions return something else that lets you control it and listen to the promise. I think it's a terrible idea what they've come up with but that's what's almost certainly gonna happen.

Generators don't suffer that problem cuz generators don't return promises, generators return iterators. Iterators give you control. I showed you the .next but there's also a .return which will abort a generator in mid run. And there's a .throw, which can manually send an error into a generator. So generators give you more control from the outside than async functions do.

It is for that reason, personally, I'm probably gonna keep using generators most of the time. Every once in a while if there's a really lightweight asynchronous process that I don't care about any external control, I'll probably use an async function. But most of the time I'm probably gonna keep using generators.

>> Kyle: It's my own take because I don't think cancellation is an important thing, a lot of times. And I don't think this design has properly taken that into account. So that's my own little personal take on it but to circle back to what we've been talking about with asynquence.

>> Kyle: I showed you that you could pass in a generator. Guess what, you can also pass in an async function. So if you did want to change, you can do that if you want, it's totally fine. Asynquence knows how to handle an asynchronous function just like it knows how to handle a generator.

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