异步Node.js模块导出


78

我想知道配置模块导出的最佳方法是什么。在下面的示例中,“ async.function”可以是FS或HTTP请求,为示例起见得到了简化:

这是示例代码(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;

如何仅在执行异步回调后导出模块?

编辑 有关我的实际用例的简短说明:我正在编写一个模块,用于在fs.exists()回调中配置nconf(https://github.com/flatiron/nconf)(即它将解析配置文件并设置nconf)。


一直在使用我的实际用例,如果使用不存在的文件调用nconf.file(),则nconf可以很好地加载,因此目前不需要解决方案。但是我仍然对该方法感兴趣。
Brett

我有同样的问题,我想导出一个Promise,并require异步加载依赖项。我认为使用babel格式化程序是可能的。但是,我认为这些不是一个好的解决方案。:(
Junle Li

Answers:


65

您的导出无法工作,因为foo声明在内部时,它在函数外部。但是,如果将导出放入内部,则在使用模块时,无法确保已定义导出。

使用异步系统的最佳方法是使用回调。您需要导出回调分配方法以获取回调,并在异步执行时调用它。

例:

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;
    }
}

async.function只是一个象征异步调用的占位符。

在主要

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

多种回调方式

如果您的模块需要多次调用,则需要管理一个回调数组:

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);
    }
}

async.function只是一个象征异步调用的占位符。

在主要

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

承诺方式

您也可以使用Promise解决此问题。此方法通过Promise的设计支持多次调用:

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

        resolve(foo);
    });
});

async.function只是一个象征异步调用的占位符。

在主要

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

请参阅Promise文档


3
如果两个单独的(主)文件在没有准备好foo的情况下调用此函数,则将无法正常工作,对吗?只有他们的回调的一个将被解雇,无论是最新称之为..
laggingreflex

在这种情况下,可以。因为我们不管理回调栈。但是用一个存储所有回调的数组很容易解决这个问题。
Techniv 2014年

Details:ReferenceError:未定义异步
-1nstinct

1
我有两个问题:(1)在您说的第一个示例中,else块的实质是什么if(typeof foo != 'undefined'){ cb(foo); // If foo is already define, I don't wait. } else { callback = cb; }?(2)这是否表示require该模块的s一直调用它直到产生一个值(来自其异步过程)?还是假设在模块的整个生命周期中仅向模块提供1个回调,即后续调用可以忽略该cb参数?
我想回答

1
@IWantAnswers,在此示例中,模块可能需要使用该foo值的不同模块需要多个时间。但是您不知道它何时发生。因此,当时间过早且该foo值不存在时,您可以存储回调以等待异步调用的返回。在异步过程结束时,所有存储的回调都将被取消堆栈,并且不再使用该数组。此时,如果另一个模块需要该模块并订阅以获取foo值,则该值已设置,因此您可以绕过存储直接执行回调。
Techniv

15

ES7方法将是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
  };
})()

可能需要稍后等待:

(async function(){

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

您能否解释调用与不调用之间的区别/含义?
贝尔纳多·达·科诺

1
如果不调用该函数,则无需执行即可导出该函数。
乔纳斯·威尔姆斯

13

ES6使用承诺回答:

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) })

或者您可以直接返回Promise本身:

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

1
这是ES6和Promises的正确,现代答案。这次真是万分感谢。
约书亚·品特

1
问题:是否有原因要返回函数而不是Promise直接返回函数?如果您Promise直接返回,则可以使用进行访问asyncFunc.then(...),对吗?很新,所以希望得到您的意见。
约书亚·品特

1
那也可以。我想当我编写此示例时,我正在导出带有异步方法的类,因此将其表述为函数。但是您可以像这样导出Promise:const p = new Promise(...); export default p;然后在导入模块中import p from '...'; p.then(...);
inostia

太好了,谢谢您的澄清。我认为这是个人喜好,还是有使用最佳实践的最佳方式?
约书亚·品特

我猜这取决于您是否需要将参数传递给异步模块,这对我来说通常就是这种情况(例如anid或其他params)。如果在第一个示例中,const asyncFunc = (id) => ...则可以id在函数中使用。你会这样称呼asyncFunc(id).then(...)。但是,如果您不需要传递任何参数,则直接返回Promise也可以。
inostia'2

10

另一种方法是将变量包装在对象内部。

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();

如果初始化程序有错误,至少您仍然会获得未初始化的值,而不是挂起回调。


3
需要模块时如何获得“ foo”?
HelpMeStackOverflowMyOnlyHope '16

var wrapper = require('wrapper'); console.log(wrapper.foo)
vangoz

9

您还可以使用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

在不同的模块中可能会发生相同的情况,并且也会按预期解决:

在其他模块中

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

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

请注意,一旦创建了Promise对象,然后按节点将其缓存。每个require('./some-async-module')实例都将返回相同的对象实例(在这种情况下为Promise实例)。


0

其他答案似乎只是部分答案,对我不起作用。这似乎有些完整:

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
});

为什么要callback(null, wrapper.foo);代替callback(wrapper.foo);
我想回答

@IWantAnswers第一个参数是错误,第二个参数是结果
tsuz '18
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.