JavaScript the Fun Part


modern-javascript

generators-use-case


Previous post was about concepts of generators; in this one we will see why we have them and where they are useful for us.

Parallel requests

If we send three requests in parallel to back-end side using AJAX ...

  1. Which request does come back first?
  2. What will be the order of request when they are coming back?

Not only it is easy to work with codes when they are synchronous but also it is easy to read them.

// synchronous code
const a = "This is a";
console.log( a );
const b = "This is b";
console.log( b );

But the same is not true for asynchronous codes.

// asynchronous code
const log = console.log.bind( console );
log( "... start ..." );

function callback( resolve, reject ){
    resolve( "this is resolve()" );
    reject( "this is reject()" );
}

const r = new Promise( callback );
log( r );   // it is not the result directly

log( ".... end ...." );

output:

... start ...
Promise { 'this is resolve()' }
.... end ....

What if we want something like this? Is it possible?

... start ...
this is resolve()
.... end ....

async-await

It may this pattern reminds you the usage of async-await keyword like so

const log = console.log.bind( console );
log( "... start ..." );

function callback( resolve, reject ){
    resolve( "this is resolve()" );
    reject( "this is reject()" );
}

const r = new Promise( callback );
log( "r in main thread:", r );   // it is not the result directly

async function asyncF(){
    const r = await new Promise( callback );
    log( "r inside asyncF():", r );
}

asyncF();

log( ".... end ...." );

output:

... start ...
r inside main thread: Promise { 'this is resolve()' }
.... end ....
r inside asyncF(): this is resolve()

Promises

Promises solve an asynchronous result for us but not completely. While we can pass two callbacks (= resolve, reject) for later invocation, but our code can be still messy, e.g. chaining many .then methods.

Generators

Because we can pause the execution of codes inside a generator function, also pull and pushing data from it, we can implement a simple data flow between our generator function and its iteration part.

const log = console.log.bind( console );
log( "... start ..." );

function* generatorF(){
    log( "start of generatorF()" );
    const a = yield 1;
    const b = yield 2;
    const c = yield 3;
    const d = yield 4;
    const e = yield 5;
    const r = a + b + c + d + e;
    log( "final result is", r );
    return "it is ready: " + r;
}

var iterator = generatorF();

let step = iterator.next();
log( "step:", step );

// loop over the iterator till is is done
while( !step.done ){
    step = iterator.next( step.value );
    log( "step:", step );
}

log( ".... end ...." );

output

... start ...
start of generatorF()
step: { value: 1, done: false }
step: { value: 2, done: false }
step: { value: 3, done: false }
step: { value: 4, done: false }
step: { value: 5, done: false }
final result is 15
step: { value: 'it is ready: 15', done: true }
.... end ....

Asynchronous synchronization

Here is a simple example to sum numbers

const log = console.log.bind( console );
log( "... start ..." );

// inside generatorF() it looks like doing 
// synchronous codes :)
// a, b, c, d, e are available when they are assigned 
function* generatorF(){
    log( "start of generatorF()" );
    const a = yield asyncF( 1 );
    const b = yield asyncF( 2 );
    const c = yield asyncF( 3 );
    const d = yield asyncF( 4 );
    const e = yield asyncF( 5 );
    const r = a + b + c + d + e;
    log( "end of generatorF(), sum:", r );
    return "it is ready: " + r;
}

var iterator = generatorF();
iterator.next();

// using a function instead of while loop
// after first call, the iterator is forwarded using the function itself
function asyncF( input ){
    const deley = Math.round( Math.random() * 1000 );
    setTimeout(function(){
        log( `asyncF( ${input} ) ...`);
        const step = iterator.next( input );
        log( "step:", step );
    }, deley );

    return "not ready yet!";
}
log( ".... end ...." );

output

... start ...
start of generatorF()
.... end ....
asyncF( 1 ) ...
step: { value: 'not ready yet!', done: false }
asyncF( 2 ) ...
step: { value: 'not ready yet!', done: false }
asyncF( 3 ) ...
step: { value: 'not ready yet!', done: false }
asyncF( 4 ) ...
step: { value: 'not ready yet!', done: false }
asyncF( 5 ) ...
end of generatorF(), sum: 15
step: { value: 'it is ready: 15', done: true }

As you can see this part is much easier to read and it looks like synchronous codes.

    const a = yield asyncF( 1 );
    const b = yield asyncF( 2 );
    const c = yield asyncF( 3 );
    const d = yield asyncF( 4 );
    const e = yield asyncF( 5 );
    const r = a + b + c + d + e;

So while we are doing asynchronous stuff, our code still looks like synchronous style. It it more readable and easy to think of it.

Generators and Promises

Now a new question arises. How can we synchronize generators with promises?

Promises can simplify our codes by later notification using a registered callback and generators can make our codes more readable.

It seems like a good and powerful combination. We will see this pattern in next post.


Update: Mon Oct 07 2019 19:30:41 GMT+0330 (Iran Standard Time)