Ben77
Ben77

Reputation: 65

asynchronous coding with promises

I am learning node and promises. I've simplified my problem and sharing my code below.

I have three modules: app.js, modLanguage.js and model.js

app.js:

var l = require('../test/modLanguage.js');

var params = [{
   p0: 'johnDoe',
   p1: '12345',
   p2: 0
}];

var lang = l.call(params);
console.log("Needs to run last: " + lang.language);

modLanguage.js:

var call = function(params) {
    var langQuery = require('../test/model.js');
    var userLang = createLanguage();

    userLang.setLanguage('FRENCH');
    console.log("This needs to run first - setting lang French before DB dip: " + userLang.language);

    var myData = langQuery.getLanguage(params)
        // My DB dip
        .then(function(data) {
            var data = JSON.parse(JSON.stringify(data[0]));
            userLang.setLanguage(data[0].Language);
            console.log("Needs to run second - DB dip brought language ENG: " + userLang.language);
            return userLang;
        })
        .catch(function(err) {
            console.log('error');
        });
    return userLang;
}
module.exports.call = call;


function createLanguage() {
    return {
        language: this.language,
        setLanguage: function(language) {
            this.language = language;
            return this.language;
        }
    }
}

model.js is a simple module which uses params, runs a stored procedure and brings back data by returning a promise.

I want to block the running of code at app.js until the object is initialized from the data returned against Database dip. However, as is, the console.log shows:

This needs to run first - setting lang French before DB dip: FRENCH

Needs to run last: FRENCH

Needs to run second - DB dip brought language ENG: ENG

What I want to achieve is obviously:

This needs to run first - setting lang French before DB dip: FRENCH

Needs to run second - DB dip brought language ENG: ENG

Needs to run last: ENG

Please, advice the changes I need to make to achieve that?

Upvotes: 0

Views: 61

Answers (2)

skylize
skylize

Reputation: 1451

Here are the lines that need to change, and why.

// Instead of assigning this promise to a variable that you never 
// even use, just return the promise to the caller, so caller can wait
// for a result to actually be there.

return langQuery.getLanguage(params)

// Returning userLang will cause your app to process userLang
// before you've attached the proper language to it. Don't return
// it. Instead, return the promise that tells you userLang will
// be ready, as described above.

return userLang; // delete this line


// Now that the promise is returned instead of an incomplete
// object, we can wait for the promise to resolve before logging.

var lang = l.call(params).then(lang => 
  console.log("Needs to run last: " + lang.language)
);


Here is a working snippet to demonstrate.

As a sidenote, var is essentially deprecated in favor of let, which behaves almost exactly the same, except for a fix for some gotcha! scoping behavior.

I think using const is better than let for every assignment statement in this example, but will use let here on your behalf, since it's a much smaller step away from using var.

If you are working in a codebase that forces you to use var, then make sure that you research the differences between var/let, otherwise just quit using var and your life will be nicer for it.

/** some mock-ups to make the example run in a snippet **/

const module = {exports: {}}

let modelMock = {
    getLanguage(params){
        return Promise.resolve([[{
            Language: 'en-us'
        }]])
    }
}

function require (path) {
    if (path === '../test/model.js')
        return modelMock;
    if (path === '../test/modLanguage.js')
        return module.exports;
}

/********* log to output pre instead of console *********/

console.log = (...args) =>
    [...args].forEach(arg =>
        document.querySelector('pre').innerText += '\n' + arg
  );

/******************* modLanguage.js *********************/

let call = function(params) {
    let langQuery = require('../test/model.js');
    let userLang = createLanguage();

    userLang.setLanguage('FRENCH');
    console.log(
        'This needs to run first - setting lang French before DB dip: '
        + userLang.language
     );

    return langQuery.getLanguage(params)
        // My DB dip
        .then(function(data) {
            // JSON.parse is known to throw. We should always wrap it
            // in a try catch block and decide how to deal with the error.
            // In this case, I'm just rethrowing with clearer message.
            try { data = JSON.parse(JSON.stringify(data[0])); }
            catch(e) { 
                e. message = 
                  'langQuery.getLanguage could not parse provided data';
                throw e;
            }
            
            userLang.setLanguage(data[0].Language);
            
            console.log(
              'Needs to run second - DB dip brought language ENG: '
              + userLang.language
            );
            
            return userLang;
        })
        .catch(function(err) {
            console.error('error');
        });
}


module.exports.call = call;

function createLanguage() {
    return {
        language: this.language,
        setLanguage: function(language) {
            this.language = language;
            return this.language;
        }
    }
}

/************************ app.js ************************/

let l = require('../test/modLanguage.js');

let params = [{
    p0: 'johnDoe',
    p1: '12345',
    p2: 0
}];

l.call(params).then(lang => 
    console.log("Needs to run last: " + lang.language)
);
<pre></pre>

Upvotes: 1

ChiefTwoPencils
ChiefTwoPencils

Reputation: 13940

You could return a promise from call. Instead of setting lang directly with the return value do l.call(...).then(...); and do your last call and set lang inside the callback.

Upvotes: 0

Related Questions