JavaScript: The Recent Parts

Async Generators with yield

Kyle Simpson

Kyle Simpson

You Don't Know JS
JavaScript: The Recent Parts

Check out a free preview of the full JavaScript: The Recent Parts course

The "Async Generators with yield" Lesson is part of the full, JavaScript: The Recent Parts course featured in this preview video. Here's what you'd learn in this lesson:

Kyle introduces a new function type as of ES2018 that allows for yield and await in the same function.


Transcript from the "Async Generators with yield" Lesson

>> Kyle Simpson: All right, so async* await functions were allowing us to implement the sync, async* pattern syntactically which is a really good thing. It improves the coding style over something like promise chains. Unfortunately there is a little bit of a conceptual limitation. To the idea of an async* await function, and that conceptual limitation is that the await keyword is essentially a pull operation.

I am pulling a value from a promise that may resolve after some amount of time. Pulling is great, but pulling is only half of what we often need to do. And we already saw another example of the other half which is when we talked about generators. We talked about generators being able to push things out, yield things out so that they can be consumed somewhere else.

So what's conceptually missing here, is that we really would like the ability to both pull and push in the same kind of function. We don't want just the generator where we can only push, or just in async* function where we can only pull. What we really would like is an async* generator.

And it turns out in ES2018, they added async* generators. This is a new function type, and they love to create new function types in JavaScript. It's a new function type, which is both an async* function and a generator in one thing. And you can use both the yield keyword and the await keyword in the same function.

The yield keyword for pushing. The await keyword for pulling. So let's take a look at a motivating example for that. Here I have an async function, and what I'm really trying to do is loop through a set of URLs and fetch out those responses get the text asynchronously and push it into an array.

And I'm having to do it all at once here because there's no way for me to pull from some ajax call and then push out the result now. I have to collect them all into an array and then do one single return with all of my results. If there's only two or three, that's not a big deal, but what if there were like 1,000 URLs?

Why would I wait to get all of those responses before returning one single array? Wouldn't it be nice if I could sort of lazily push a response out every single time I got a response from the ajax request. And if I can push it out then that means that somebody else could be lazily consuming that as a data source.

They could be saying hey just let me know every time you get another response back just iterate me again using the dot dot dot or the four over, something like that conceptually. Wouldn't it be nice if you could consume asynchronously a data source like that. Well theoretically if we were to switch this from an async* function into a generator then theoretically we could actually support that because on line three when I call yield.

Because there is a runner here I'm yielding out a promise. So I'm using the yield keyword as a pull mechanism. And then later when I use yield here, I'm using yield, the same keyword but I'm now using it as a push mechanism to push out a result to somebody who might be consuming it.

Now you could theoretically write a runner that was smart enough that if you yield out a promise then it waits, and if you yield out a non-promise, then it sends it out through an iterator interface. You could write something like that, but can you just see that conceptually, this is bad programming?

Or this is confusing programming, to overload the meaning of the yield keyword to mean two entirely different, opposite things? Pulling and pushing. It doesn't make sense. And it would be confusing to manage this code and not be able to juggle, wait, what is this yield keyword doing? Is it pulling or is it pushing?

So that's the motivation for why we want to have a function type that can both await, to pull things, and yield, to push them. So that's where we get async generators. We have the async word on the front, and then the star in there. So now we get both, it's like the the child of the async* function and the generator, they got together and now we have async* generators.

And you'll notice now that I can call a wait to listen for a pull of a promise, like an ajax call. And then I can use yield to push out a value.

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