返回异步数据,然后在 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 需要解决,链接.thens 和返回Promises 一起变得太丑陋了。

依赖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 上游调用ABconst 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 中同步导出的主要内容,如果未能解决你的问题,请参考以下文章

关于Node.js异步转同步

提供 Promise 作为模块的导出是不是是 Node.js 中异步初始化的有效模式?

Node.js 同步循环或迭代异步语句

同步和异步编程有啥区别(在node.js中)

Nodejs异步编程

Nodejs异步编程