Sessions is temporarily moving to YouTube, check out all our new videos here.

JavaScript Generators

Bruno Godefroy speaking at London Node User Group in August, 2017
186Views
 
Great talks, fired to your inbox 👌
No junk, no spam, just great talks. Unsubscribe any time.

About this talk

Generators are widely used in Node and JavaScript libraries to enable developers to write powerful code in a simple way. Have you ever found yourself completely lost in front of a yield statement? Where does my promise go? How can I get the result back? We will try to answer these questions,and go into a step-by-step decomposition of redux-saga to understand how this wonderful library uses generators.


Transcript


Hi everyone, I'm going to talk about Java script generators today and how to write simple asynchronous code and in a beautiful way. I'm working as a full-stack developer at Theodo. So Theodo is an agency for web and mobile developments and working for big corporations as well as young start ups. I will try to help them implementing their solution using Scrum and Lean. Here's my details also, so if you want to give me a shout on either of these should work, feel free. So what I was going to talk about today, I will talk about the generators. So first I will go into details like, "What is a generator?". Because maybe some of you don't really know exactly what a generator is. Then I will talk about one library called Co which maybe you have heard of, which helps you to write asynchronous code using generators. And then I will talk about how Co is using Koa. So for those who do not know Koa is the node framework like Express. So lets start with generators. What is a generator? So you have several terms you have to understand what it is. The first one is generator function. Generator function looks a bit like that. It looks like a classic function, but with a star and a yield statement instead of a return. You can have several yields in the same. So this is not the generator. This is just something that would define a generator. And to get a generator, which is also sometimes called a generator object, you need to call that function. So when you call generator function you get generator objects. And generator objects will expose a next method to go to the next step of the generator. Since its exposing this next method a generator is also an iterator. You might have heard this word before. So iterator java script is any object that exposes a next method. So with basic knowledge now of the terms to understand the rest. A very simple example. How would you count to three using generators? So here are my generator functions, really simple. First yield one, then two and they stop by returning three. I initiate my generator by calling the function count to three and then I call next three times. And when you call the next functions in generator you get back an object that contains two attributes. First one is value. And its value is exactly what you yielded or returned. And the second one is done. Is the generator done? Its always equal to false if you are yielding. And it's equal to true when you are returning. And you hit return only once in a generator. Now let's see how we can pass an argument with a generator. So here is an example of a counter and if you can see I'm doing a while, infinite while true loop. An infinite loop. Its probably one of the only places we can do this safely. Generators are always posing so you can easily, like, this generator will never end. But it doesn't really matter because if I don't call next function it's OK. So here I just want to define my should increments being the result of my yields. And if I asked increments I will just increment the counter. And I am calling the generic term next, several times. The first time I just start the generator. I get the four, which it my neutral value. And then if I call gen next if True I'm setting should increments as true for the rest of the execution and it's looping again and again you can see. If I say gen next if true I'm actually incrementing the counter. And if I say false I'm not. So some other really useful feature. When you use a generator you need to be careful of what we call closure. Here's a generator I'm doing actually a map over an array. And, inside a map I'm using a function of course and if I try to use yield it's not going to work. Because yield is only defined inside the generator and here in my map I'm actually inside a function. So I cant call to the generator. So this generator is actually broken. So it is something to be careful about. You can't really use functional programming in generators. Or at least you can't yield inside functional programming. And now maybe something that is quite true because maybe you are wondering how can I use the generator. One common way of using it is to iterate through a generator. So since it is an iterator we can call the next function. So here my generator is the second block, you can see the greeting generator. And this defining a name being the result of my yield. A yield some type of word object containing one, the first attribute being a function. The second one being arguments. And I yield twice. First one I will yield get name is new argument. Second time I will yield console log with a string of arguments. But if you look at my iterate generator function what happens is that I do again an infinite loop so just run until generator is over. And what I do is I get back the done on value from the result of the yields by calling gen not next. And if I'm not done I will just use define my next parameter from my gen not next being equal to the result of the function call with the arguments. So doing that I will actually, if you look at the greeting generator again, You will see that name will be the result of get name with no arguments. And function return Bruno, very simple. And the next step I will console log "Hello my name is" and the value of name. So I will see "Hello my name is Bruno". So this is maybe the simplest way of iterating for generator by passing the result of the previous yield to the next step of the generator. So now we're gonna look into Co. So Co is the library that is used to be able to write a asynchronous code in a synchronous way. Because if you're not yielding functions and calling them what you do is you yield promises. So here I have two functions that return promises of get name and get score. The first one result is Bruno, the second one result is 12. I have my generator. I will first define my name. The result of the yield of get name. Second one different score, the result of the get score of the name. And then I will execute my generator by wrapping my generator in Co. If you give me a promise and when I do promise dot then I get the value which will be the return value of my generator. And it looks quite nice, its very easy to read and real easy to understand when you are new even to generators. But maybe there's some magic behind it which is what we are going to look into now. So we will try to live code and recreate co from scratch. So here we go. This is exactly the example I just showed you before. The same method returning promises, the same generator. So now if I run I get Bruno 12. Exactly what I expected. So then what we're gonna do is we're just gonna remove co and we're gonna define it together. So co is a function. It takes generator objects and it returns a promise. New promise. So define a promise. Resolve. Reject. And do some stuff with it. So how we are going to proceed, I would like to start my generator to run. For that I will just call run of gen dot next. This is ready to start the execution. So now interesting part is what is the run function that I want to commence. So the run function. So the run function will take one argument which will be the result of the yielded value from the next step of the generator. So its an object containing value and done. And what do we do with that? So if its done, this is the easiest one to start with. If its done it means I'm returning a value. Its probably the last value of my generator. So I just want to return it as my string at the end. So we're going to return it so we return as resolve. Value. Call. Now what happens if its not done then value is probably a promise. Where one of the promises returned by get name or get score. So what I want to do is I want to do value dot then and then return that. And then I will go into one unfulfilled function. So what happens when everything goes well? Unfulfillied. Oops. Takes one argument with the result of my promise. And do something with it. And what I want to do is I want to call the next step of my generator with the parameter being the result of the previous promise. For that I can just call run again with gen dot next of results. So here if I don't do any mistakes when I run I go to the first step. It will yield some value with the return value of my promise. And I will initiate the generator for the next step. Say if I try but I do a mistake, apparently I did. Brackets, around that, I suppose. I should get my Bruno 12, like so right here. But I still have a problem. So I need to resolve that. No actually, I resolve at the end. OK. Its always the same. So now what happens if something in here doesn't go well during your promise? So let's see, I will change a bit my promise. Here I will just get an ID. And return depending on the value of the ID. If ID equals one, return promise, resolve, else, return. Promise. Resolve this one. And now, my get score will take the name as the parameter. And return the different value depending on the name. So if name is Bruno. I return the same thing. Else I will reject. Simple one, rejected an error. So now if I run my generator I need to instant check my generator with an ID. So lets say if I take something else done once I take two. I should have an exception. OK so I have rejected a define. Oh, yes, of course. Yes, promise. You're right. Cool. Error, rejection error. So we get exactly what I rejected. So now once maybe code to be able to handle that. So for that we need to be able to handle something when a promise doesn't resolve. So then we have on failure. And what we are going to do on on failure is actually almost the same as when everything goes well. The only difference is that instead of calling next we want to call the throw method of the generator. Which is the way to tell the generator something went wrong. We have raise an error. Say pass it back. Now if I want to handle that in my generator function I can just do a try catch as classics increment programming. Catch, "e". Let's see if I run it. I'm still rejected. Get scores rejected with error. And when you run it gets caught in the yield. Then you should go on failure. On failure its supposed. OK. OK, that's probably why. OK I guess I'm lost in my live coding. So I gave the easiest way to show you the solution. Although that's far from it. That's far from it. And so in the same co I've seen the function before. Here we have it the same way. In the unfulfilled function we have almost the same with the return gen next of the results. Error, we return the throw. So I don't see where I did my mistake, but probably is typo somewhere. And then what happens next, the next step once you handle maybe, is that if the generator is failing itself, lets see, if I use throw, at the end of my generator, you need to be able to try catch your run function. So you wrap your return run into a try catch and return wherever you want to explain to the user that actually his generator is failing and that he should probably check his code. So I should just go ahead and emphasise, we see that we rewrote code in, like, 20-ish lines. And we almost have everything, the only thing missing compared to the real code. But right now I can only yield promises. The real code you can yield other kinds of objects like values or function, schematics. But the only thing you have to do is complete this if here and do all the different cases and then you can, you can recode the real Co. So if you want to have a look at the gist you can go to my git hub. There is 20 line of Co and you can look at them to understand details. How it works. Why would you use Co? Co is used most everywhere. This is the NPM package, screenshots. There's like 3,800 differences. You can see how some are from like, Koa here. This is probably why we cannot go to the next approximator which is Koa version one. Because, why version one? Because version two is now using Async await. But version one is using generators. So as I said before Koa is not framework very close to Express as a type of middleware. The difference with Express is that each middleware is a generator. And this is an example of the Hello World from the presentation. So define, you want your app to use two middleware. The first one is a logger, to log how long the request takes to go through. The second one is to return Hello World. And, how it works is, when you yield next you actually go to the next generator. So this first part of Co here is it continues to find a new date. Then going to the this body equal hello world and then coming back to checking how many milliseconds this has taken. And. How Koa is working? This is the next simplified version of Koa in 20 lines again. And, the important part here, is a lot of details, maybe its not that difficult but it takes maybe a little while to understand. But the important part is the call backs. This is the call back of the http node server. And, what it takes. It takes request response as usual. It generated context. The context that would be shared by all generators. The generators, we actually only do one thing. That is to modify their context. Their context should contain all information about the response, the headers, everything you can imagine. And, after that, is just one big promise resolution. So you just wrap in Co one middleware. And then you just do dot then. If everything went well you just returned the response with the body of the context. And if there is an error you just console the error. What happens. Maybe you're wondering what is this composed function? This composed function is actually taking one of each of the middleware you stack and returning a super middleware containing all of them. And how they are run, you've got this diagram. So you have the time, on the y axis. And first you start seeing middleware one is running instruction. As soon as you have a yield next, next is actually a promise. It's actually a Co promise. Next middleware routing to Co. Since we run the next generator as a promise but next generator is also yielding next which is also a promise. So we go to the third generator which would be run when third generator is over the promise is resolved. So you come back to the previous one. The same. The end of the previous one is executed. The promise is resolved and you come back to the first one. You can finish the execution. This is how you create a stack of middleware using generators. So that all of them are actually a simple promise that is generated using Co. Now like Co is using Async await in the new version. So Async await is probably like the new way of writing asynchronous code in nodes. In java script in general. If for any reason you are stuck into an old version of node because you have an... ... or you can't update because of finances. Then generators are available since node 4.8. Whereas, Async await is only for version 7.6. So generators are quite an old picture on nodes and allow you to do some very powerful stuff. And some times actually you need generators you cannot eve use only Async await. I think for ... for front end developers. You need to do something about what you are using you can't do that with Async await. Async await you just have to wait for it to be resolved. For Co it works really well. So for Co I outsource then you can just use Async await instead. That was all for me today. And, if you have any question. Feel free to grab me afterwards. I will be here. And, all the tools, piece of code I showed you today, they are available on github. You always wondered how Koa or Co was working, feel free to check out. Thank you.