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

Max Stoiber
InstructorMax Stoiber
Share this video with your friends

Social Share Links

Send Tweet
Published 8 years ago
Updated 6 years ago

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.

[00:00] 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.

[00:13] 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.

[00:33] 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.

[00:46] 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.

[01:04] 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.

[01:19] 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.

[01:38] 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.

[01:56] 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.

[02:10] 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.

[02:29] 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.

[02:49] 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.

[03:09] 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.

[03:30] 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.

[03:53] 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.

[04:11] 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.

[04:29] 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.

[04:51] 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.

[05:10] 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.

[05:23] 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.

[05:38] 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.

[06:02] 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.

[06:17] 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.

[06:40] 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.

Pawel Grzybek
Pawel Grzybek
~ 8 years ago

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.

Max Stoiber
Max Stoiberinstructor
~ 8 years ago

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

Paul Vincent Beigang
Paul Vincent Beigang
~ 8 years ago

How does a comparison between generators and observables look like?

Raphi
Raphi
~ 8 years ago

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.

Anshuman
Anshuman
~ 8 years ago

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.

Victor Hazbun
Victor Hazbun
~ 7 years ago

THIS IS AMAZING!

Alex
Alex
~ 7 years ago

good course last lesson could be done with await and it would work, it would be good if you would incorporate side effect on yields to show why this could be better used.

redux-saga for examples provides great use cases for generators.

Austin Hunt
Austin Hunt
~ 5 years ago

Doesn't async await make this no longer very useful?

Niv B
Niv B
~ 5 years ago

@Austin In did, but even now there are some enterprises and system codes still writing like.

Mova.Club
Mova.Club
~ 3 years ago

Many thanks for your great job!

Amir Mousavi
Amir Mousavi
~ 3 years ago

Fantastic one!

~ 3 years ago

Thank you for the course Max, I enjoyed the videos. But out of curiosity - does this have any benefits over async..await? (I understand the videos are quite old but still wanted to know your thoughts on this)

Tre' Codez
Tre' Codez
~ 2 years ago

Can use this url for quotes: https://api.quotable.io/random

Markdown supported.
Become a member to join the discussionEnroll Today