等待模块导入异步初始化的最佳方法?
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的文章。
【讨论】:
以上是关于等待模块导入异步初始化的最佳方法?的主要内容,如果未能解决你的问题,请参考以下文章