Generators in JavaScript - What, Why and How - FunFunFunction #34

By: Fun Fun Function

1267   40   62182

Uploaded on 05/30/2016

Generators are (kind of) pausable functions in JavaScript. Another word for them is co-routines. They are used (among other things) to manage async operations, and play very well with promises.

I'm also active on:
► Twitter https://twitter.com/mpjme
► Medium https://medium.com/@mpjme
► Quora https://www.quora.com/profile/Mattias-Petter-Johansson

Resources:

► Recursion in JavaScript
https://www.youtube.com/watch?v=k7-N8R0-KY4&list=PL0zVEGEvSaeEd9hlmCXrk5yUyqUag-n84&index=7

► Promises in JavaScript
https://www.youtube.com/watch?v=2d7s3spWAzo&index=8&list=PL0zVEGEvSaeEd9hlmCXrk5yUyqUag-n84

► ES6 JavaScript features Playlist (videos like this one)
https://www.youtube.com/playlist?list=PL0zVEGEvSaeHJppaRLrqjeTPnCH6vw-sm

► Joakim Karud (Background music in episode)
https://soundcloud.com/joakimkarud

Comments (5):

By anonymous    2017-09-20

I would not like to read your code for you but i think, with a little help you could understand this code your self. I guess you need help with the various new syntax being used in the code above. I'll try to note down those so that you can understand all of this code yourself.

(0, (_asyncToGenerator2 || _load_asyncToGenerator()).default)(function*{})

This line basically is similar to

(0,x)(function*{}) 

where x is a function which takes a generator function as an argument.

Whenever you have a line of the form (x,y) it will always return the last value. So in the case of(x,y) it will return y. If its (0,x) it will return x. Thus in code which you posted, the first line will return (_asyncToGenerator2 || _load_asyncToGenerator()).default.

You could now translate the code to

((_asyncToGenerator2 || _load_asyncToGenerator()).default)(function* {})

This means that above code will return a function which takes a generator as argument

If you need more information on generator you could go here The generator function has attributes like yield. They are pretty useful especially to handle asynchronous operations. It streamlines your code and makes it easy to read. To get more information what yield means, you could go here and here

You could also see some lines like these in the code.

builtInCommand(curDir, ...process.argv.slice(3));

This is basically spread operators being used. Spread operators basically allow an expression to be expanded in places where multiple arguments are expected. You could go here to know more about spread operators.

Hope you will be able to read the above code yourself after you understand the concepts.

Original Thread

By anonymous    2017-09-20

Objectives

When I started this thread, I had two objectives in mind:

  1. Having error differentiation
  2. Avoid an if then else of doom in a generic catcher

I have now come up with two radically distinct solutions, which I now post here, for future reference.

Solution 1: Generic error handler with Errors Object

This solution is based on the solution from @Marc Rohloff, however, instead of having an array of functions and looping through each one, I have an object with all the errors.

This approach is better because it is faster, and removes the need for the if validation, meaning you actually do less logic:

const errorHandlers = {
    ObjectRepeated: function(error){
        return { code: 400, error };
    },
    SomethingElse: function(error){
        return { code: 499, error };
    }
};

Survey.findOne({
        _id: "bananasId"
    })
    .then(doc => {

        //we dont want to add this object if we already have it
        if (doc !== null)
            throw { reason: "ObjectRepeated", error:"Object could not be inserted because it already exists."};

        //saving empty object for demonstration purposes
        return new Survey({}).save();
    })
    .then(() => console.log("Object saved with success!"))
    .catch(error => {
        respondToError(error);
    });

    const respondToError = error => {
        const errorObj = errorHandlers[error.reason](error);

        if (errorObj !== undefined)
            console.log(`Failed with ${errorObj.code} and reason ${error.reason}: ${JSON.stringify(errorObj)}`);
        else 
            //send default error Obj, server 500
            console.log(`Generic fail message ${JSON.stringify(error)}`);
    };

This solution achieves:

  1. Partial error differentiation (I will explain why)
  2. Avoids an if then else of doom.

This solution only has partial error differentiation. The reason for this is because you can only differentiate errors that you specifically build, via the throw {reaon: "reasonHere", error: "errorHere"} mechanism.

In this example, you would be able to know if the document already exists, but if there is an error saving the said document (lets say, a validation one) then it would be treated as "Generic" error and thrown as a 500.

To achieve full error differentiation with this, you would have to use the nested Promise anti pattern like the following:

.then(doc => {

    //we dont want to add this object if we already have it
    if (doc !== null)
        throw { reason: "ObjectRepeated", error:"Object could not be inserted because it already exists." };

    //saving empty object for demonstration purposes
    return new Survey({}).save()
        .then(() => {console.log("great success!");})
        .catch(error => {throw {reason: "SomethingElse", error}});
})

It would work... But I see it as a best practice to avoid anti-patterns.

Solution 2: Using ECMA6 Generators via co.

This solution uses Generators via the library co. Meant to replace Promises in near future with a syntax similar to async/await this new feature allows you to write asynchronous code that reads like synchronous (well, almost).

To use it, you first need to install co, or something similar like ogen. I pretty much prefer co, so that is what i will be using here instead.

const requestHandler = function*() {

    const survey = yield Survey.findOne({
        _id: "bananasId"
    });

    if (survey !== null) {
        console.log("use HTTP PUT instead!");
        return;
    }

    try {
        //saving empty object for demonstration purposes
        yield(new Survey({}).save());
        console.log("Saved Successfully !");
        return;
    }
    catch (error) {
        console.log(`Failed to save with error:  ${error}`);
        return;
    }

};

co(requestHandler)
    .then(() => {
        console.log("finished!");
    })
    .catch(console.log);

The generator function requestHandler will yield all Promises to the library, which will resolve them and either return or throw accordingly.

Using this strategy, you effectively code like you were coding synchronous code (except for the use of yield).

I personally prefer this strategy because:

  1. Your code is easy to read and it looks synchronous (while still have the advantages of asynchronous code).
  2. You do not have to build and throw error objects every where, you can simply send the message immediately.
  3. And, you can BREAK the code flow via return. This is not possible in a promise chain, as in those you have to force a throw (many times a meaningless one) and catch it to stop executing.

The generator function will only be executed once passed into the library co, which then returns a Promise, stating if the execution was successful or not.

This solution achieves:

  1. error differentiation
  2. avoids if then else hell and generalized catchers (although you will use try/catch in your code, and you still have access to a generalized catcher if you need one).

Using generators is, in my opinion, more flexible and makes for easier to read code. Not all cases are cases for generator usage (like mpj suggests in the video) but in this specific case, I believe it to be the best option.

Conclusion

Solution 1: good classical approach to the problem, but has issues inherent to promise chaining. You can overcome some of them by nesting promises, but that is an anti pattern and defeats their purpose.

Solution 2: more versatile, but requires a library and knowledge on how generators work. Furthermore, different libraries will have different behaviors, so you should be aware of that.

Original Thread

By anonymous    2017-12-18

Well the promise hasn't been resolved, so what you can do is use async await functions or JavaScript generators which will make the client wait till the rating is incremented and the results json is sent.

Here's a tutorial on async-await and generators.

Original Thread

Popular Videos 373

Submit Your Video

If you have some great dev videos to share, please fill out this form.