This lesson is for PRO members.

Unlock this lesson NOW!
Already subscribed? sign in

Use JavaScript (ES6) generators with Promises to handle async flows

6:52 JavaScript lesson by

With one tiny utility function we’ll unlock the full power of generators to make them work well with Promises and thus be the perfect weapon for asynchronous flows in our apps.

Get the Code Now
click to level up

egghead.io comment guidelines

Avatar
egghead.io

With one tiny utility function we’ll unlock the full power of generators to make them work well with Promises and thus be the perfect weapon for asynchronous flows in our apps.

Avatar
Austin

Awesome work with these 6 lessons! I'd love to see more from you :)

One thing I'm confused about with generator functions is how they differ from async/await. Or rather, why you would ever want to use generator functions instead of async/await.

It seems as though perhaps generator functions are the "building blocks" of async/await, and that async/await wraps the yielded value in a promise. (This seems like what co is doing as well, so that seems to me like a third-party lib for async/await.)

If that is the case, generator functions are certainly great to understanding the inner mechanics of async/await, but is there an application in which they would be more practical than async/await?

Please enlighten me if I'm incorrect or mistaken about any of this.

In reply to egghead.io
Avatar
Austin

Ah, I see now that generator functions exist as a standard in ES6, whereas async/await didn't make that release (but will likely be in ES7). So for developers who cannot use a transpiler like Babel, async/await is still out of reach, but generator functions can be used today without a transpiler (unless you're looking to support IE).

In reply to Austin
Avatar
Paweł Grzybek

Fantastic course! Short and easy to watch / understand with a morning coffee. The last lesson enlightened me a lot — finally I understand the inner logic of async / await functions which in my eyes are essentially a combination of promises and generators. Thanks a ton for this course, looking forward for more your resources! Have a great day.

Avatar
Max Stoiber

Thank you for the kind words, glad you enjoyed it 😊

In reply to Paweł Grzybek
Avatar
Max Stoiber

That is the first reason, and the second reason is that Generators make libraries like redux-saga possible. Those won't work with async/await, as they need to communicate with the generator function via dedicated helpers. See for example this section of the redux-saga docs, which shows how easy it is to test sagas: https://redux-saga.github.io/redux-saga/docs/basics/DeclarativeEffects.html That wouldn't be possible with async/await!

In reply to Austin
Avatar
Paul Vincent Beigang

How does a comparison between generators and observables look like?

Avatar
Raphi

Hi Max,

The videos were informative. I'm wondering however, in your last video where you use generators and Promises together to fetch some data, wouldn't it be much simpler to just use Promises in that fetchQuote generator (or rather, fetchQuote function)?

I fail to see what the generator+Promises adds in this case. Perhaps you were just trying to give an idea of the capabilities but used a not so stellar example.

Avatar
Dr.Emmett

it's short but awesome!

btw, which editor did you use while coding in this sessions?

Thanks

In reply to egghead.io
Avatar
Anshuman

A Promise-based implementation of fetchQuote() would consist of a chain of .then() calls. While there is nothing wrong with that, using yield instead allows you to write code that looks almost like a synchronous version of fetchQuote() would.

Imagine that fetch() is synchronous and picture the body of fetchQuote() without the yield keyword.

In reply to Raphi
Avatar
Jaime

I like a lot of the course. Good job!

In reply to egghead.io

Here we have a very simple three lines of code generator called create quote fetcher. It fetches some data from an URL, in our case an API, which returns a random inspirational quote every time you call it.

It fetches that and then takes the response, parses the response that we get back from the API, and then returns the quote that we got, slightly formatted as a string. What's nice about these three lines of code is that they look very synchronous. There's no callbacks or promises, or anything.

It just looks kind of synchronous, which is to say constresponse equals yield fetch URL. Constcode equals yield response of JSON return a string with formatted quote. This is possible because generators are pausable and resumable.

We pause whenever a promise comes in. When that promise comes in, we resolve the yield with the result of the promise, and parse that back to the generator. This is what we see down here. We create an instance of the generator and then we start to generate with the first or next call.

This will pause at the first yield key word, and return whatever is to the right of it -- in our case, fetch URL which is a promise. The value of the quote fetch at the next call is a promise. It's tenable, so we type that in, and wait until it's resolved.

Our generator at this point has just paused, there's nothing happening there. It's paused at this yield keyword. When the problem resolves, that means when we've got the data from the API, we take the response we got and start our generator again. We type quote fetch.next and parse in the response.

This yield keyword will get that response which we parse in, and parse it back to the variable on the left-hand side. It will continue executing until we choose the next yield keyword in the next line, which again parses us back out the response that JSON called, which is another promise. This value again, is a promise.

We return that and then wait for that to resolve. When that's done, when the response JSON has been parsed, we take the parsed response, the result, and start our generator again, which is at this point paused at the second yield keyword.

We parse back in the parsed JSON of the response which we say appears in the const quote variable, and then take the value. The generator continues executing and returns the formatted string, so this value is just a quote. What we do down here is we say, quote and then consol.logout the quote that we got.

If we run this code you will see that -- I should probably save it -- it gives us a random inspirational quote. Every single time we call it, again it's another one. The interesting part about these two lines down here is that they're very generic. There's nothing fancy going on.

What they do is, they make the yield keyword sort of like a message buzz. They wait for something to the right side of it to resolve, and then parse the result of that back into the generator. There's nothing fancy going on. It's very generic behavior. What I want to show you is how we can make that generic.

How can we make this whole part generic and just get rid of it, just get rid of all of this and write it like this? That would be nice. Guess what? We can do that without changing our generator at all. Our generator will still look like perfectly synchronized beautiful code, but we're going to need the helper function.

Let's create that. We're going to call it "quote routine." It will get parsed to the generator, and then instantiate the generator by just calling it. In our case, we're going to wrap our create quote fetcher call in call routine. This is our create quote fetcher. The const generator now instantiates the create quote fetcher.

Then what we want to do is, we want to start our generator. We return generator.next. This only goes to the first pause. We want to do this recursively for every single time the generator pauses. We want to wait until the promise resolves, and then parse the result back out.

What we do is, we write a little helper function called handle. This helper function will get the result that we got from generator.next and then do something with it. We're going to wrap this down here in handle, and call it with the first instance of our generator.

Now, in here we want to wait until the result value has resolved. If this is a promise what generator.next returns, we want to wait until it resolves and then just parse it back into the generator. We get the result whatever that may be. It's very generic, and parse it back to our generator by doing generator.next result.

This is still not recursive, because it'll only do it for the first yield. What we actually want to do is, we want to parse this result whatever we get back, back into handle. Now, every single time our generator pauses, this handle function will take the result, wait for it to resolve, take the result and parse it back into the generator.

If we run this you will see that this doesn't quite work. It's an infinite loop, because we never stopped executing. It's infinitely recursive. What we need to do is, we need to check if the result that we got says that done is true.

If we're at the end of the generator we don't want to recursively call handle any more. All we want to do is just promise that resolve result.value. There's no need to call handle any more because we're at the end of the generator.

If we run this, nothing happens because I forgot to return the result here. There we go. Now, if we type node.js we get a quote back. With 10 lines of very generic helper function, we made it possible to write synchronous-looking code that is absolutely asynchronous, and use it very simply too.

Basically, we got our cake and we could eat it too. This is amazing. This interplay between generates and promises is great. It makes it possible to write very nice-looking and easy to use asynchronous flows for your applications.

If you are thinking, "I don't want to write this co-routine function every time I want to have a nice, asynchronous flow in my application," that's good because there's a module for that. Instead of writing our own, we can also MPM install code which is an MPM module, get rid of this part, and then just say, const.co equals require code.

Then replace our co-routine call down with code. It will still work exactly the same. If we run node.index.js we still get back a quote.

HEY, QUICK QUESTION!
Joel's Head
Why are we asking?