返回异步数据,然后在 Node.js 中同步导出
Posted
技术标签:
【中文标题】返回异步数据,然后在 Node.js 中同步导出【英文标题】:Returning asynchronous data then exporting it synchronously in Node.js 【发布时间】:2021-05-17 10:36:30 【问题描述】:背景
我正在从 AWS Secrets Manager 返回数据并使用 aws-sdk
执行此操作。早些时候我问了一个关于如何正确返回数据并将其导出的问题,因为导出的对象在导出导入其他地方时从未解析过数据。这导致我得到一堆未定义的。
在解决了这个问题后,确定处理这个问题的方法是将 aws-sdk 函数包装在一个 Promise 中,然后在另一个文件中使用 async await 调用该 Promise。这给我带来了问题。
示例
如果我像这样从 AWS 请求并返回数据,
let secrets =
jwtHash: 10,
;
const client = new AWS.SecretsManager(
region: region
);
const promise = new Promise((resolve, reject) =>
client.getSecretValue( SecretId: secretName , async (err, data) =>
if (err)
reject(err);
else
const res = await JSON.parse(data.SecretString);
secrets.dbUsername = res.username;
secrets.dbPassword = res.password;
secrets.dbHost = res.host;
secrets.dbPort = res.port;
secrets.dbDatabase = res.dbname;
resolve(secrets);
);
);
module.exports = promise;
然后我可以将它导入另一个文件并使用这样的数据,
const promise = require('../secrets');
(async () =>
const secrets = await promise;
// use secrets here
)();
现在假设在我尝试使用机密的那个文件中,我有这样的东西,
const pool = new Pool(
user: secrets.dbUsername,
host: secrets.dbHost,
database: secrets.dbDatabase,
password: secrets.dbPassword,
port: secrets.dbPort
);
pool.on('error', err =>
console.error('Unexpected error on idle client', err);
process.exit(-1);
);
module.exports = pool;
如果我将 pool
函数包装在异步自调用函数中,我将无法导出它,以便在我需要数据库连接时可以在我的应用程序的任何地方使用它。类似地,我的应用程序中有许多需要访问机密数据的功能。如果我要遍历将所有代码包装在异步函数中的应用程序,它将继续导致更多这些困难。
问题
在我看来,这里最好的解决方案是异步返回数据,一旦解决,同步导出。
如何在这种情况下完成这样的任务?
这里的胜利将是,
-
在 /secrets/index.js 中提出请求
在同一个文件中构建 secrets 对象
将机密导出为对象,无需异步函数即可轻松导入应用程序的其他任何位置。
我想如何使用它的示例
const secrets = require('../secrets');
const pool = new Pool(
user: secrets.dbUsername,
host: secrets.dbHost,
database: secrets.dbDatabase,
password: secrets.dbPassword,
port: secrets.dbPort
);
pool.on('error', err =>
console.error('Unexpected error on idle client', err);
process.exit(-1);
);
module.exports = pool;
【问题讨论】:
【参考方案1】:因为所需的数据是异步获取的,所以无法将依赖它的所有内容(以某种方式)也设为异步。在涉及异步性的情况下,一种可能性是通常导出可以按需调用的函数,而不是导出对象:
在数据返回之前无法有意义地导出依赖于异步数据的对象 如果您导出函数而不是对象,您可以确保控制流从您的单个入口点开始并流向下游,而不是每个模块都同时初始化自身(当某些模块可能会出现问题)如您所见,依赖于其他人来正确初始化)另一方面,请注意,如果您有一个需要解析的Promise
,则在其上调用.then
可能比使用async
函数更容易。例如,而不是
const promise = require('../secrets');
(async () =>
// try/catch is needed to handle rejected promises when using await:
try
const secrets = await promise;
// use secrets here
catch(e)
// handle errors
)();
你可以考虑:
const promise = require('../secrets');
promise
.then((secrets) =>
// use secrets here
)
.catch((err) =>
// handle errors
);
它不那么冗长,而且可能更容易一目了然 - 比自我调用 async
IIFE 更好。 IMO,使用await
的地方是当你有多个 Promises
需要解决,链接.then
s 和返回Promise
s 一起变得太丑陋了。
依赖secrets
执行的模块必须 在其代码中具有有效等待secrets
填充的内容。虽然能够在您的较低代码示例中使用您的const secrets = require('../secrets');
将是很好,但这是不可能的。您可以导出一个将secrets
作为参数 而不是require
的函数,然后(同步!)return
实例化pool
:
// note, secrets is *not* imported
function makePool(secrets)
const pool = new Pool(
user: secrets.dbUsername,
host: secrets.dbHost,
database: secrets.dbDatabase,
password: secrets.dbPassword,
port: secrets.dbPort
);
pool.on('error', err =>
console.error('Unexpected error on idle client', err);
process.exit(-1);
);
return pool;
module.exports = makePool;
然后,要在另一个模块中使用它,一旦创建了secrets
,使用secrets
调用makePool
,然后使用/传递返回的pool
:
const secretsProm = require('../secrets');
const makePool = require('./makePool');
secretsProm.then((secrets) =>
const pool = makePool(secrets);
doSomethingWithPool(pool);
)
.catch((err) =>
// handle errors
);
请注意,doSomethingWithPool
函数可以是完全同步的,makePool
也是如此 - secrets
的异步特性,一旦在 one 中使用 .then
处理> 模块,不必在其他任何地方异步处理,只要其他模块导出函数,而不是对象。
【讨论】:
CertainPerformance 再次来袭... :|这段代码唯一要做的就是每次调用它时,都会创建一个新的 Pool 实例。所以......您将无法跨不同的文件轻松访问相同的实例。我的代码是相反的。它不能轻易地创建新实例,但它在任何文件中引用同一个实例。 @TJBlackman我实际上只是在打字询问这个问题。我很震惊,这让我如此悲伤。我接受了这个,因为根据我的确切问题,它对我来说更有意义。我也很感激你。 @TJBlackman 这个想法是使用相同类型的函数模式 everywhere - 如果模块 A 需要pool
而模块 B 需要相同的 pool
,则模块 A 和模块 B 导出以pool
为参数的函数,并使用创建的pool
上游调用A
和B
。 const pool = makePool(secrets); const moduleA = setupModuleA(pool); const moduleB = setupModuleB(pool); /* do stuff with moduleA and moduleB */
这就是依赖注入。
在过去的 2 个小时里我一直在更新我的应用程序,这无疑是一种比我现有的更好的做事方式。再次感谢@CertainPerformance 的帮助。
听起来很棒而且很正式,我想我只是没有看到将模块导出为带参数的函数的好处,与导出返回的函数相比价值。甚至没有争论它,只是从来没有遇到过 1 失败而其他成功的情况。如果可以的话,我今天将阅读有关依赖注入的内容。【参考方案2】:
我建议在 1 个文件中完成所有操作,然后不要导出您创建的对象,而是导出一个返回对象的函数。该函数将始终可以访问对象的最新版本,您可以从任何文件调用它来访问同一对象。
示例: 在一个文件夹中创建两个文件。在第一个文件中,我们将这样做:
定义一个值。 设置超时以在一段时间后更改值 导出值本身 导出一个返回值的函数values.js
let x = 0 ; // set initial value
setTimeout(() => x = 5; , 2000); // sometime later, value will change
const getValueOfX = () => return x; ;
module.exports =
x: x,
getValueOfX: getValueOfX
;
现在在另一个文件中,我们只是从前一个文件中导入两个导出(我们将它们都放在一个对象中以便于导出)。然后我们可以注销它们,等待一段时间过去,然后再次注销它们。
index.js
let values = require('./values');
console.log(`Single value test. x = $values.x`);
console.log(`Function return value test. x = $values.getValueOfX()`);
setTimeout(() => console.log(`Single value test. x = $values.x`); , 4000);
setTimeout(() => console.log(`Function return value test. x = $values.getValueOfX()`); , 4000);
要运行代码,只需打开终端或命令提示符,然后在与这两个文件相同的目录中运行 node index.js
您会看到,当仅导出值(对象、数组、w/e)时,它会在导出运行时按原样导出 - 几乎总是在 API 调用完成之前。
但是 - 如果您导出一个返回值的函数(对象、数组、w/e),那么该函数将在调用它时检索该值的最新版本!非常适合 API 调用!
所以您的代码可能如下所示:
let secrets = jwtHash: 10 ;
const client = new AWS.SecretsManager(
region: region
);
let pool = null;
client.getSecretValue( SecretId: secretName , async (err, data) =>
if (err)
reject(err);
else
const res = await JSON.parse(data.SecretString);
pool = new Pool(
user: res.username,
host: res.host
database: res.dbname
password: res.password
port: res.port
);
pool.on('error', err=>
console.error('Unexpected error on idle client', err);
process.exit(-1);
);
);
module.exports = function() return pool; ;
【讨论】:
以上是关于返回异步数据,然后在 Node.js 中同步导出的主要内容,如果未能解决你的问题,请参考以下文章