JavaScript the Fun Part


modern-javascript

promises-confusing-part


We saw the good part of the promises which was handing two functions for us asynchronously, fn1, fn2.

Here is the code from previous post:

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

setInterval(function(){
    log( "simulating main task is running ..." );
}, 1000);

function backendStuff( index, resolve, reject ){
    setTimeout(function(){
        try{ 
            // throw "we have error in network!";
            while( ++index < 100000000 );
            resolve( "backendStuff done: " + index );
        } catch( ex ){
            reject( "backendStuff exception: " + ex );
        }
    }, 3000);
    // this is run as main task
    return index;
}

function callback( fn1, fn2 ){
    const r = backendStuff( 0, fn1, fn2 );
    // this is run as main task
    log( "start from:", r );
}

const p = new Promise( callback );
p.then( r => log( r ) ).catch( e => log( e ) );
log( p );
log( "---- end ----" );

and it outputs:

--- start ---
start from: 0
Promise { <pending> }
---- end ----
simulating main task is running ...
simulating main task is running ...
backendStuff done: 100000000
simulating main task is running ...
simulating main task is running ...
simulating main task is running ...

Synchronizing the output

If you look at this part:

function callback( fn1, fn2 ){
    const r = backendStuff( 0, fn1, fn2 );
    // this is run as main task
    log( "start from:", r );
}

You can see that it is more like a regular function that has a return value which is going to be assigned to the variable r.

But based on the output we can realize that this does not happen.

--- start ---
start from: 0                 // it is 0 but we want 100000000
Promise { <pending> }
---- end ----

How can we solve this problem? How can we use a promise just like a regular function that has a return statement.

Return value of a promise

For understanding it lets look at a simpler example.

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

function toUpperCaseLater( string ){
    return new Promise(function( resolve ){
        setTimeout(function(){
            resolve( string.toUpperCase() );
        }, 3000);
    });
}


function asycn2UpperCase( input ){
    const lowerCase = input;
    log( "lowerCase:", lowerCase );
    const  upperCase = toUpperCaseLater( lowerCase );
    log( "upperCase:", upperCase );
}

asycn2UpperCase( "how are you today?" )
console.log( "---- end ----" );

it outputs:

--- start ---
lowerCase: how are you today?
upperCase: Promise { <pending> }
---- end ----

And you can see the output is a Promise Object with the state of pending.

At this point we can say ohhhh! We did not use then() method part of a promise. Good idea. Lets try it.

function asycn2UpperCase( input ){
    const lowerCase = input;
    log( "lowerCase:", lowerCase );
    const  upperCase = toUpperCaseLater( lowerCase ).then( r => r );
    log( "upperCase:", upperCase );
}

It outputs:

--- start ---
lowerCase: how are you today?
upperCase: Promise { <pending> }
---- end ----

If it seems wired to do not worry. Lets try print the output from then() method.

function asycn2UpperCase( input ){
    const lowerCase = input;
    log( "lowerCase:", lowerCase );
    const  upperCase = toUpperCaseLater( lowerCase ).then( r => { log( r ) } );
    log( "upperCase:", upperCase );
}

output

--- start ---
lowerCase: how are you today?
upperCase: Promise { <pending> }
---- end ----
HOW ARE YOU TODAY?

Okay now we are able to see the output but we want to be assigned to upperCase variable. In fact we want that it can be seen as a regular function!

Reviewing core idea

Lets review the core idea to see what happens here.

In the previous post we realized that two registered functions fn1 and fn2 will be run later as a micro task and not part of the main task.

And this means the promise (= toUpperCaseLater()) function we have when it is called it returns nothing valuable for us, just like the first example

    }, 3000);
    // this is run as main task
    return index;
}

This return statement is part of main task. The same is true for toUpperCaseLater() function which means it returns a promise and that is why we see

upperCase: Promise { <pending> }

It is the state of the promise right now.

Solution

How can we synchronize our code with toUpperCaseLater() function?

Simple! By passing a function to be executed later by the then() method of our promise.

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

function toUpperCaseLater( string ){
    return new Promise(function( resolve ){
        setTimeout(function(){
            resolve( string.toUpperCase() );
        }, 3000);
    });
}
/*
function asycn2UpperCase( input ){
    const lowerCase = input;
    log( "lowerCase:", lowerCase );
    const  upperCase = toUpperCaseLater( lowerCase ).then( r => { log( r )} );
    log( "upperCase:", upperCase );
}
*/
function asycn2UpperCase( input ){
    const lowerCase = input;
    log( "lowerCase:", lowerCase );
    
    // wrap our code in a wrapper function
    function sync( data ){
        const upperCase = data;
        log( "upperCase:", upperCase );
    }

    // pass wrapper function to then()
    toUpperCaseLater( lowerCase ).then( r => sync( r ) );

    // no code after toUpperCaseLater() function
}
asycn2UpperCase( "how are you today?" )
console.log( "---- end ----" );

And the output

--- start ---
lowerCase: how are you today?
---- end ----
upperCase: HOW ARE YOU TODAY?

Refactoring

We can do it differently. We can pass our sync() function to be run later.

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

function toUpperCaseLater( string, sync ){
    // bind first argument to sync
    sync = sync.bind( null, string );

    return new Promise(function( resolve ){
        setTimeout(function(){
            resolve( sync );
        }, 1000);
    });
}

function asycn2UpperCase( input ){
    const lowerCase = input;
    log( "lowerCase:", lowerCase );

    toUpperCaseLater( lowerCase, sync ).then( r => r() );

    // these lines are asynchronous
    function sync( data ){
        const upperCase = data.toUpperCase();
        log( "upperCase:", upperCase );
    }
}
asycn2UpperCase( "how are you today?" )
console.log( "---- end ----" );

output

--- start ---
lowerCase: how are you today?
---- end ----
upperCase: HOW ARE YOU TODAY?

Async-await

What you saw is what async-await does for us. In fact the reason why we have async-await is because of simplifying this strange look and usage of promises.

We review async-await in next post.


Update: Mon Sep 30 2019 08:19:00 GMT+0330 (Iran Standard Time)