Asynchronous nodejs module exports

node.jsAsynchronous

node.js Problem Overview


I was wondering what the best approach is for configuring a module export. "async.function" in the example below could be a FS or HTTP request, simplified for the sake of the example:

Here's example code (asynmodule.js):

var foo = "bar"
async.function(function(response) {
  foo = "foobar";
  // module.exports = foo;  // having the export here breaks the app: foo is always undefined.
});

// having the export here results in working code, but without the variable being set.
module.exports = foo;

How can I export the module only once the async callback has been executed?

edit a quick note on my actual use-case: I'm writing a module to configure nconf (https://github.com/flatiron/nconf) in an fs.exists() callback (i.e. it will parse a config file and set up nconf).

node.js Solutions


Solution 1 - node.js

Your export can't work because it is outside the function while the foodeclaration is inside. But if you put the export inside, when you use your module you can't be sure the export was defined.

The best way to work with an ansync system is to use callback. You need to export a callback assignation method to get the callback, and call it on the async execution.

Example:

var foo, callback;
async.function(function(response) {
    foo = "foobar";
	
	if( typeof callback == 'function' ){
        callback(foo);
    }
});

module.exports = function(cb){
    if(typeof foo != 'undefined'){
	    cb(foo); // If foo is already define, I don't wait.
	} else {
	    callback = cb;
	}
}

Here async.function is just a placeholder to symbolise an async call.

In main

var fooMod = require('./foo.js');
fooMod(function(foo){
    //Here code using foo;
});

Multiple callback way

If your module need to be called more than once you need to manage an array of callback:

var foo, callbackList = [];
async.function(function(response) {
    foo = "foobar";
	
    // You can use all other form of array walk.
    for(var i = 0; i < callbackList.length; i++){
        callbackList[i](foo)
    }
});

module.exports = function(cb){
    if(typeof foo != 'undefined'){
	    cb(foo); // If foo is already define, I don't wait.
	} else {
	    callback.push(cb);
	}
}

Here async.function is just a placeholder to symbolise an async call.

In main

var fooMod = require('./foo.js');
fooMod(function(foo){
    //Here code using foo;
});

Promise way

You can also use Promise to solve that. This method support multiple call by the design of the Promise:

var foo, callback;
module.exports = new Promise(function(resolve, reject){
    async.function(function(response) {
        foo = "foobar"

        resolve(foo);
    });
});

Here async.function is just a placeholder to symbolise an async call.

In main

var fooMod = require('./foo.js').then(function(foo){
    //Here code using foo;
});

See [Promise documentation][1]

[1]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise "Promise on MDN"

Solution 2 - node.js

An ES7 approach would be an immediatly invoked async function in module.exports :

module.exports = (async function(){
 //some async initiallizers
 //e.g. await the db module that has the same structure like this
  var db = await require("./db");
  var foo = "bar";

  //resolve the export promise
  return {
    foo
  };
})()

This can be required with await later:

(async function(){

  var foo = await require("./theuppercode");
  console.log(foo);
})();

Solution 3 - node.js

ES6 answer using promises:

const asyncFunc = () => {
    return new Promise((resolve, reject) => {
        // Where someAsyncFunction takes a callback, i.e. api call
        someAsyncFunction(data => {
            resolve(data)
        })
    })
}

export default asyncFunc

...
import asyncFunc from './asyncFunc'
asyncFunc().then(data => { console.log(data) })

Or you could return the Promise itself directly:

const p = new Promise(...)
export default p
...
import p from './asyncModule'
p.then(...)

Solution 4 - node.js

Another approach would be wrapping the variable inside an object.

var Wrapper = function(){
  this.foo = "bar";
  this.init();
};
Wrapper.prototype.init = function(){
  var wrapper = this;  
  async.function(function(response) {
    wrapper.foo = "foobar";
  });
}
module.exports = new Wrapper();

If the initializer has error, at least you still get the uninitialized value instead of hanging callback.

Solution 5 - node.js

You can also make use of Promises:

some-async-module.js

module.exports = new Promise((resolve, reject) => {
    setTimeout(resolve.bind(null, 'someValueToBeReturned'), 2000);
});

main.js

var asyncModule = require('./some-async-module');

asyncModule.then(promisedResult => console.log(promisedResult)); 
// outputs 'someValueToBeReturned' after 2 seconds

The same can happen in a different module and will also resolve as expected:

in-some-other-module.js

var asyncModule = require('./some-async-module');

asyncModule.then(promisedResult => console.log(promisedResult)); 
// also outputs 'someValueToBeReturned' after 2 seconds

Note that the promise object is created once then it's cached by node. Each require('./some-async-module') will return the same object instance (promise instance in this case).

Solution 6 - node.js

Other answers seemed to be partial answers and didn't work for me. This seems to be somewhat complete:

some-module.js

var Wrapper = function(){
  this.callbacks = [];
  this.foo = null;
  this.init();
};
Wrapper.prototype.init = function(){
  var wrapper = this;  
  async.function(function(response) {
    wrapper.foo = "foobar";
    this.callbacks.forEach(function(callback){
       callback(null, wrapper.foo);
    });
  });
}
Wrapper.prototype.get = function(cb) {
    if(typeof cb !== 'function') {
        return this.connection; // this could be null so probably just throw
    }
    if(this.foo) {
        return cb(null, this.foo);
    }
    this.callbacks.push(cb);
}
module.exports = new Wrapper();

main.js

var wrapper = require('./some-module');

wrapper.get(function(foo){
    // foo will always be defined
});

main2.js

var wrapper = require('./some-module');

wrapper.get(function(foo){
    // foo will always be defined in another script
});

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionBrettView Question on Stackoverflow
Solution 1 - node.jsTechnivView Answer on Stackoverflow
Solution 2 - node.jsJonas WilmsView Answer on Stackoverflow
Solution 3 - node.jsinostiaView Answer on Stackoverflow
Solution 4 - node.jsvangozView Answer on Stackoverflow
Solution 5 - node.jsefidilesView Answer on Stackoverflow
Solution 6 - node.jsTakuView Answer on Stackoverflow