等待模块导入异步初始化的最佳方法?

Posted

技术标签:

【中文标题】等待模块导入异步初始化的最佳方法?【英文标题】:Best way to wait for asynchronous initialization from a module import? 【发布时间】:2018-07-09 19:22:34 【问题描述】:

TL;DR:有没有办法等待具有异步功能的模块导入完成,然后在调用模块中继续执行以保持模块功能的包含?

我正在开发一个个人节点项目,随着代码库的不断增长,我一直在以模块化/OOP 方式构建该项目。一项要求是启用跨模块/对象的日志记录,其中可以在不同时间记录不同的日志文件。我认为通过创建一个带有 init 函数的 Logger.js 文件,我已经以一种非常干净的方式解决了这个问题,只需在我需要的任何模块中导入 Logger.js 文件,我就可以随时使用该文件。以下是用于说明这一点的精简代码:

Logger.js

module.exports.init = function(location) 
    var logFileBaseName = basePath + fullDatePathName;
    var studentLogFile = fs.createWriteStream(logFileBaseName + '-student.log', flags : 'a');
    var teacherLogFile = fs.createWriteStream(logFileBaseName + '-teacher.log', flags : 'a');

    this.studentLog = function () 
        arguments[0] = '[' + Utils.getFullDate() + '] ' + arguments[0].toString();
        studentLogFile.write(util.format.apply(null, arguments) + '\n');
    
    this.teacherBookLog = function () 
        arguments[0] = '[' + Utils.getFullDate() + '] ' + arguments[0].toString();
        teacherLogFile.write(util.format.apply(null, arguments) + '\n');
    

这看起来很棒,因为在我的主要入口点我可以简单地做:

Main.js

const Logger = require('./utils/Logger');

Logger.init(path);
Logger.studentLog('test from Main');

// all my other code and more logging here

在我的其他几十个文件中,我可以做的更少:

另一个文件.js

const Logger = require('./utils/Logger');

Logger.studentLog('test from AnotherFile')

然后要求不仅要记录到“学生日志”的文件,还要记录到 Discord(聊天客户端)。看起来很简单,我有这个 Logger 文件,我可以初始化 Discord 并在“学生日志”旁边登录到 Discord,如下所示:

Logger.js

module.exports.init = function(location) 

    // code we've already seen above

    var client = new Discord.Client();
    client.login('my_login_string');
    channels = client.channels;

    this.studentLog = function () 
        arguments[0] = '[' + Utils.getFullDate() + '] ' + arguments[0].toString();
        var message = util.format.apply(null, arguments) + '\n';
        studentLogFile.write(message);
        channels.get('the_channel_to_log_to').send(message)
    

    // more code we've already seen above

问题是如果你再次重新运行 Main.js,studentLog 会失败,因为 .login() 函数是异步的,它返回一个 Promise。登录尚未完成,当我们尝试调用 Logger.studentLog('test from Main'); 时,频道将是一个空集合

我已经尝试在 Logger.js 中使用 Promise,但在 Logger.js 中返回 Promise 之前,Main.js 的执行当然会继续。如果 Main.js 可以简单地等到 Discord 登录完成,我会喜欢它。

我的问题是,在保持我一直使用的模式的同时,最好的方法是什么?我知道我可以将整个 main 函数包装在等待 Discord 登录完成的 promise.then() 中,但这对我来说似乎有点荒谬。我试图将功能包含在模块中,并且不希望这种 Logger 代码/逻辑溢出到我的其他模块中。我想将它保留为一个简单的 Logger 导入,就像我一直在做的那样。

任何建议都会很棒!

【问题讨论】:

您不想在.then 中将客户端登录包装在logger.js 中吗?即client.login(token).then(success=>//everything else);,因为这似乎是最简单的。 拥抱异步模式,放弃同步模式。以后你会过上幸福的生活。 @Wright 我确实尝试在 Logger 中用 .then() 包装它。问题是在 Logger 到达 .then() 之前 main 仍在继续,这意味着它会在完全初始化之前尝试使用 Discord 客户端。 @trincot 目标肯定是拥抱异步模式,但是如何实现呢?您是否建议放弃将所有 Logger 逻辑包含在 Logger.js 中,并使用等待 Discord 的 login() 函数返回的承诺包装我的所有 Main.js 代码? 您肯定需要将所有依赖于异步结果的代码放入回调中(用于承诺的then 回调)或async/await 构造:两者都涉及函数包装器。当然,您可以从此类回调中调用其他函数。 【参考方案1】:

让我为“异步初始化记录器”问题提供我的解决方案。请注意,这仅涉及日志记录,很可能无法一概而论。

基本上,所有消息都附加到一个队列,只有在设置了指示连接就绪的标志后才会发送到远程位置。

例子:

//Logger.js
module.exports = 
  _ready: false,
  _queue: [],

  init(): 
    return connectToRemote().then(()=>this._ready = true)
  ,

  log(message): 
    console.log(message);
    _queue.push(message)
    if (this._ready) 
      let messagesToSend = this._queue;
      this._queue = [];
      this._ready = false;
      sendToRemote(messagesToSend).then(()=>this._ready = true);
    
  



您可以在任何文件中要求记录器并立即使用日志功能。只有在您可以随时调用的init函数解决后才会发送日志。

这是一个非常简单的示例,您可能还想限制队列大小和/或仅在特定时间间隔内批量发送日志,但您明白了。

【讨论】:

【参考方案2】:

如果等待某个异步函数的结果然后在同一个调用者函数中使用,则首先解析结果,然后再使用。如果结果在另一个函数或模块中使用(例如,结果被分配给一个全局变量),它不会被解析。在您的情况下,如果 client.login() 异步为 client.channels 赋值,则该赋值等待,channels = client.channels 赋值会将 undefined 赋值给 channels

如 cmets 中所述,要解决此问题,您必须使用回调或从 client.login() 返回承诺。

您可以参考this的文章。

【讨论】:

以上是关于等待模块导入异步初始化的最佳方法?的主要内容,如果未能解决你的问题,请参考以下文章

导入模块的最佳方法Python [重复]

导入特定于版本的 python 模块的最佳方法

模块的导入方法

使用最佳实践反应导入模块 [重复]

Angular 导入的模块不等待 APP_INITIALIZER

使用最佳实践反应导入模块[重复]