Use Generators, Promises & recursion to write synchronous like asynchronous code!

Use Generators, Promises & recursion to write synchronous like asynchronous code!

In JavaScript, functions execute until they've reached a return keyword or the end of the function. If we execute our logger here and run it with Node, you will see that it first logs start, and it then logs end. EA6 generators are different. They can be paused and resumed at arbitrary points within the function.

function helloWorld() {
    console.log("Hello world!")
}

helloWorld() // Hello world!

This is a pretty basic function that logs a string in console when called and returns undefine.

To create a generator, we put an asterisk after the function keyword. If we execute this file again, you will see that nothing happens. Nothing is console.logged out. That is because when we call a generator, it returns an instance of itself but doesn't execute it.

function* helloWorld() {
    console.log("Hello world!")
}

helloWorld() // ...

To be more exact, we should call our generator createHelloWorld, and assign it to a variable called helloWorld. To execute our generator, we call helloWorld.next, which executes until the next block is over. If we run this, you'll see again that we logged 'Hello world!' and we logged end.

function* createHelloWorld() {
    console.log("Hello world!")
}

const helloWorld = createHelloWorld()
helloWorld.next() // Hello world!

To pause a generator, we use the yield keyword. The yield keyword tells the generator to stop executing at this point. If we run our file again, you will see this logs out start. The generator never reaches the console.log('world!'). To execute the next block as well, we call helloWorld.next again. As you can see, both 'Hello' and 'world!' were logged out.

function* createHelloWorld() {
    console.log("Hello")
    yield
    console.log("world!")
}

const helloWorld = createHelloWorld()
helloWorld.next() // Hello
helloWorld.next() // world!

We have a generator here and an instance of a generator. These two can communicate. You can send messages from the instance of the generator back to the generator function, and you can send messages from the generator itself to the instance of the generator.

Let's say we want to send a message from the generator to our instance of the generator. We can do so with a yield keyword, putting any value that we want to the right of it. This value will now be returned from the hello.next call that we do, which pauses the generator, this yield keyboard, and returns this value.

If we run our function, we will see that it doesn't only return the value, but it actually returns an object which has a value property of first, which is the value we pass through, and another property called done, which is currently set to false.

Done is set to false because we haven't yet finished executing our generator. It is still paused at this yield keyword. To finish executing the generator, we simply call hello.next again. The second log that I've put out, now has a value of undefined, but done is true now because our generator is now at the end of the function and has finished executing.

If we want to pass a value from our instance of the generator back to the generator function, we can pass a value when we resume the generator with hello.next and assign it to any variable. In our case, I'll call it word, and then console.logout whatever we pass in.

I'm going to pass in a string that says max and start our function again to see what happens. This time, both of our hello.next calls return the value of undefined. We also got max logged out because we logged out the word which we passed in when we resumed the generator.

Using generators with promisses

Lets start by creating a helper function called coroutine, thats gonna help us handle generators like async await statements

const coroutine = (gen) => {
    const generator = ger()

    const handle = (result) => {
        return result.done 
          ? Promise.resolve(result.value) 
          :  Promise.resolve(result.value)
              .then(res => handle(generator.next(res)))
    }

    return handle(generator.next())
}

Lets understand this function. First, we receive a generator and instanciate it, then, we define a function 'handle' wich resolve and recall the generator if its not ended. And last, we handle the generator first call, this way we use promises with recursion to handle an async await like functionality:

function* generatorAsyncLikeFunction() {
    const response = yield fetch(url)
    const parsedResponse = yield response.json()
    return parsedResponse;
}

const coroutine = (gen) => {
    const generator = ger()

    const handle = (result) => {
        return result.done 
          ? Promise.resolve(result.value) 
          :  Promise.resolve(result.value)
              .then(res => handle(generator.next(res)))
    }

    return handle(generator.next())
}

const fetcher = coroutine(generatorAsyncLikeFunction)
fetcher.then(res => console.log(res))

This article was possible thanks to egghead.io academy