【问题标题】:Asynchronous nodejs module exports异步 nodejs 模块导出
【发布时间】:2021-10-12 02:36:19
【问题描述】:

我想知道配置模块导出的最佳方法是什么。下面示例中的“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 加载正常,所以现在我不需要解决方案。但我仍然对这种方法感兴趣。
  • 我有同样的问题,我想导出一个承诺,require 异步加载依赖项。我认为使用 babel 格式化程序是可能的。但是,我认为这不是一个好的解决方案。 :(

标签: node.js asynchronous


【解决方案1】:

您的导出无法工作,因为它在函数之外,而 foodeclaration 在内部。但是如果你把导出放在里面,当你使用你的模块时,你不能确定导出是否已经定义。

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

例子:

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 documentation

【讨论】:

  • 如果两个单独的(主)文件在 foo 未准备好的情况下调用此函数,这将不起作用,对吧?只有一个回调会被触发,以最近调用它的为准。
  • 在这种情况下,是的。因为我们不管理回调堆栈。但是用一个数组来存储所有回调很容易解决这个问题。
  • 详细信息:ReferenceError: async is not defined
  • 我有 2 个问题:(1)在你说if(typeof foo != 'undefined'){ cb(foo); // If foo is already define, I don't wait. } else { callback = cb; } 的第一个例子中,else 块的本质是什么。 (2) 该块是否意味着该模块的requires 会不断调用它,直到它产生一个值(从它的异步过程中)?或者它是否假设在模块的整个生命周期内只会给模块提供 1 个回调,即后续调用可以省略 cb 参数?
  • @IWantAnswers,在这个例子中,模块可以被需要使用foo 值的不同模块多次要求。但你不知道它是什么时候发生的。因此,当它还早且 foo 值不存在时,您存储回调以等待异步调用的返回。在异步过程结束时,所有存储的回调都被取消堆栈,并且不再使用数组。此时如果另一个模块需要这个模块并订阅获取foo的值,该值已经设置好了,所以绕过s​​tore直接执行回调。
【解决方案2】:

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

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

如果初始化器有错误,至少你仍然得到未初始化的值而不是挂起回调。

【讨论】:

  • 需要模块时如何获得“foo”?
  • var wrapper = require('wrapper'); console.log(wrapper.foo)
【解决方案3】:

你也可以使用 Promise:

some-async-module.js

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

ma​​in.js

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

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

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

in-some-other-module.js

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

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

请注意,promise 对象创建一次,然后由节点缓存。每个require('./some-async-module') 都会返回相同的对象实例(本例中为promise 实例)。

【讨论】:

    【解决方案4】:

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

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

    ma​​in.js

    var wrapper = require('./some-module');
    
    wrapper.get(function(foo){
        // foo will always be defined
    });
    

    ma​​in2.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 第一个参数是错误,第二个是结果
    【解决方案5】:

    ES6 使用 Promise 回答:

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

    【讨论】:

    • 这是 ES6 和 Promises 的正确现代答案。谢谢你。
    • 问题:您是否有理由直接返回一个函数而不是Promise?如果您直接返回Promise,您可以使用asyncFunc.then(...) 访问它,对吗?很新,所以想听听你的意见。
    • 这也行。我想当我写这个例子时,我正在导出一个带有异步方法的类,所以它就像一个函数一样。但是你可以像这样导出 Promise:const p = new Promise(...); export default p; 然后在你的导入模块中import p from '...'; p.then(...);
    • 太棒了,感谢您澄清这一点。我想这是个人喜好还是有使用其中一种的最佳实践方式?
    • 我想这取决于您是否需要将参数传递给异步模块,这对我来说通常是这种情况(例如,id 或其他参数)。在第一个示例中,如果 const asyncFunc = (id) =&gt; ... 那么您可以在函数中使用 id。你可以称它为asyncFunc(id).then(...)。但是如果你不需要传递任何参数,直接返回 Promise 也是可以的。
    【解决方案6】:

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

    【讨论】:

    • 你能解释一下调用它和不调用它的区别/含义吗?
    • 如果你不调用函数,你导出函数而不执行它。
    • 太棒了。应该是公认的答案。
    猜你喜欢
    • 2019-10-28
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多