Rethinking Asynchronous JavaScript

Async 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 "Async 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:

Kyle demonstrates how generators can be used in asynchronous applications. The yield keyword will block a generator while an async call is being executed. Values returned from the async call can be passed back to the generator allowing it to resume and sequentially trigger other async operations.


Transcript from the "Async Generators" Lesson

>> [MUSIC]

>> Kyle Simpson: Here's our meaning of life running example, written a little bit more clearly to prove this point about asking questions. Look at line 6. I say x is equal to 1 + and then I make an AJAX call. And I yield while the AJAX call is finishing, okay.

That AJAX call, getData, returns me nothing right away. But you'll notice that 1,000 milliseconds after it runs, up on line two, what do we call up on line two? We call run to restart, to resume the generator with the value that we got back. So after 1000 milliseconds, our fake AJAX gives us the response from our AJAX call.

And where does that value come back? Right back to the yield expression on line 6. So we get the value 10 right back.
>> Kyle Simpson: So what we have here is synchronous looking asynchronous code.
>> Kyle Simpson: We are able to block locally inside of our generator as if there is some magical pause resume thing.

Because there is a pause resume now, and it's the generator. The plumbing of the generator is able to pause, wait for some other background thing to finish. And then resume with the value that we were waiting for. So inside of our generator, line six through eleven, that's our program logic.

That's the stuff we write over and over and over again. Lines one through three and line fifteen, that's all plumbing that gets hidden away in some library somewhere. Our program logic, our flow control looks like 6 through 11. Now, if you mentally picture that compared to twenty slides ago or whatever, when we went over the promise chain.

Promise chains are a form of flow control that ends up vertically orienting a bunch of function calls into a promise chain. And if you think even further back, we had thunks and they were nested together and callbacks, they had all that kind of weirdness. So here we have flow control again.

But what's so magic about this particular flow control is that it looks synchronous. Why is that so important? Because that's the way our brains work. We don't have to have any of that overhead, that boilerplate of passing things in as callbacks and chaining things and returning. All that stuff gets hidden away, it's an implementation detail.

What we've done is taking asynchronicity itself and factored it out as an implementation detail that we do not care about.
>> Kyle Simpson: I'm not really sure that the full weight of what I just said is actually coming across. That's a huge deal. Taking asynchronicity itself and factoring it out so that we don't have to worry about it.

>> Kyle Simpson: We write synchronous, sequential, blocking looking code. And we let the library handle all of the ugly bits.
>> Speaker 2: Does it get the same performance like if it the getData, if it's not making an external call, it's a function in itself and it's making some huge operation? Will it get the same performance like if I do with a web worker or something where I have to do an asynchronous-.

>> Kyle Simpson: No, no, no, no, no, don't get this confused with threads, okay. If the behavior is happening in your JavaScript program, it is taking up the single event thread. This is not the same thing as messaging out to a web worker. Could you message out to a web worker on line two?

Absolutely. But line 2 doesn't magically become like a web worker just because you called it from a generator. There's still just one thread for our JavaScript program to run. The only magic that's different here than anything else you've ever written is the fact that line 6 through 11 get to magically locally block.

We never had that capability syntactically before. And now we have a syntactic way of hiding away all of the complexity of a state machine, an asynchronous state machine. So our code looks synchronous again. And not only does our regular code looks synchronous, our error handling becomes synchronous again.

How do we do synchronous error handling, what do we use?
>> Kyle Simpson: Try, catch. You can wrap try catch around line 6. And if the AJAX under the covers threw some error, that error is gonna come right back in and get caught as a synchronous error. Because inside of the generator, everything's back to normal, synchronous, blocking semantics including error handling.

So we get an asynchronous stack that gets normalized back into a synchronous stack inside of our generator. Yeah.
>> Speaker 2: Line 11, console.log(answer)-
>> Speaker 2: So the logging wait until x plus 1 is computed?
>> Kyle Simpson: Line 11 doesn't run until after line 8 is finished. Line 7 doesn't run until after line 6 is finished.

>> Kyle Simpson: Because of that yield keyword.
>> Speaker 2: But if I have line 12, that's console.log something else, that would be printed.
>> Kyle Simpson: Then it's gonna go right away. We only pause at the yield keyword. Everything else keeps running until the next yield keyword. Or until the end and then it finishes.

>> Speaker 3: Technically, line 8, you don't need to do a yield there because you're really not conceptually fetching anything, right? That's just computing something local.
>> Kyle Simpson: In all of our running meaning of life example, we always externalized all three calls.
>> Speaker 3: Okay.
>> Kyle Simpson: So I'm doing the exact same thing.

We're pretending as if that string needs to get sent off somewhere and localized into our own alien language or whatever. And then it comes back to us, okay. Yes?
>> Speaker 4: They're asking again, on line 16, if they had put a run there, Would that execute after calling setTimeout?

>> Kyle Simpson: If on line 16 you put a run there, it would resume the generator that's waiting on line 6 right away, not sending it a value. And when the setTimeout tried to run call run, if the generator was not in a currently paused state, that would create an error.

So you do not want to try to have lots of different people trying to resume your generator in lots of different places. That's a really terrible idea. The generator can only, only one thing can ever be happening in JavaScript at any given time. So we don't have that problem.

But you don't want to take that and have lots of different people calling it, cuz you'll get out of sequence.
>> Kyle Simpson: I see a question in here about using promises in the generator. Hold on, we'll get there. I promise, we're almost there.
>> Kyle Simpson: I just wanna let a moment for this to sink in.

Cuz this is like that holy crap moment. Like, whoa, I get to do synchronous looking asynchronous programming?
>> Kyle Simpson: I saw some people asking earlier what are some use cases of this. The very first time I saw this was five or six years ago before there were generators in the standard.

Firefox put generators into their implementation of JavaScript engine about a decade ago. And I was working at Firefox on the developer tools team. And every time we would add a feature to a developer tool, of course we needed a test case for it, a test suite for some feature.

And everything you do in the Firefox browser that you're doing in JavaScript, it's all asynchronous. There's no synchronous anything, every single API is asynchronous. Well, it gets really, really hard to write asynchronous test cases. When every single like draw border and then wait for that to happen. And the real problem is that none of those calls, I mean, they're all asynchronous.

But none of them have events to let you know that they finished. So you literally are just like doing setTimeout, I mean, it was crazy, terrible to write test cases in that environment. Because it was asynchronicity without any notification that the step had completed. So you can imagine that my test cases were horrible.

And then one day one of my coworkers was like well, why don't you just rewrite all with a generator? And I was like what? And I started reading through and I was like, my God. I can write my test cases in a synchronous fashion and let all that asynchronicity crap, all the timeouts and stuff, hide that away?

It changed everything.
>> Kyle Simpson: I saw that years before we ever got a hope of that landing as part of the JavaScript standard. But I've been a big fan for a long time because synchronous looking async is that magical silver bullet. And you don't get those very often in programming language design.

Those magical silver bullets that come out of nowhere.
>> Kyle Simpson: So when I said earlier that promises are great and promise chains give us some flow control. But, and I even said that I did the asynquence library and gave me self sequences. And then I said that actually, I don't use those anymore, this is why.

Cuz I write a 100% of my asynchronous code inside of generators. If you have the option of writing synchronous looking async, why on earth would you ever want to write a promise chain again? I don't know, I can't come up with a single case why a promise chain would ever be what I wanna write, when I have that option.

So I've gone back and completely rechanged my whole way of thinking. Once I got to understanding the power of generators, I've gone back and completely changed my way of thinking. And now what I do is put everything inside of 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