[英]Returning asynchronous data then exporting it synchronously in Node.js
背景
我正在从 AWS Secrets Manager 返回数据并使用aws-sdk
来执行此操作。 早些时候我问了一个关于如何正确返回数据并将其导出的问题,因为导出的 object 在导出到其他地方时从未解析过数据。 这导致我得到了一堆未定义的。
解决该问题后,确定处理此问题的方法是将 aws-sdk function 包装在 promise 中,然后使用异步等待在另一个文件中调用 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
function 包装在异步自调用 function 中,我将无法导出它,因此当我需要数据库连接时,它可以在我的应用程序中的任何地方使用。 类似地,我的应用程序中有许多功能需要访问秘密数据。 如果我要遍历将我的所有代码包装在异步函数中的应用程序,它将继续导致更多这些困难。
题
在我看来,这里最好的解决方案是异步返回数据,一旦解决,就同步导出。
在这种情况下如何完成这样的任务?
这里的胜利是,
我想如何使用它的示例
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;
因为需要的数据是异步获取的,所以没有办法让依赖它的所有东西(以某种方式)也异步。 由于涉及异步性,一种可能性是通常导出可以按需调用的函数,而不是导出对象:
另一方面,请注意,如果您有一个需要解析的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
小号一起变得太丑陋。
依赖于一个模块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
,一旦与处理.then
在一个模块中,并没有要处理的异步其他地方,只要其他模块中导出函数,而不是对象。
我建议在 1 个文件中完成所有操作,然后不要导出您创建的对象,而是导出一个返回对象的函数。 该函数将始终可以访问对象的最新版本,并且您可以从任何文件调用它来访问同一对象。
示例:在一个文件夹中创建两个文件。 在第一个文件中,我们将这样做:
值.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
};
现在在另一个文件中,我们只需从前一个文件中导入两个导出(我们将它们都放在一个对象中以便于导出)。 然后我们可以将它们注销,等待一段时间过去,然后再次注销。
索引.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; };
我做的一件事(尤其是在处理导入已移动到数据库的 static 变量的大型应用程序时)是通过 function 加载该文件,然后 function 填充导出。
// config.js
const exports = {};
export async function populate() {
const RUNTIMEVARS = await what_you_do_to_get_vars();
for (const config of RUNTIMEVARS) {
exports[config.key] = exports[config.data];
}
// for anything needing the config in the bootstrap.
return exports;
}
export default exports;
然后在引导程序中:
// bootstrap.js
import './database-connection.js'; // important to have no internal dependencies.
(async() => {
const { populate } = await import('./config.js');
await populate();
import('./application/index.js');
})()
现在,您的应用程序中的任何文件都可以import config from '../config.js'
就好像它是静态声明的一样,因为我们在引导程序中填充 function 时填充了 object。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.