【问题标题】:Returning asynchronous data then exporting it synchronously in Node.js返回异步数据,然后在 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 函数包装在异步自调用函数中,我将无法导出它,以便在我需要数据库连接时可以在我的应用程序的任何地方使用它。类似地,我的应用程序中有许多需要访问机密数据的功能。如果我要遍历将所有代码包装在异步函数中的应用程序,它将继续导致更多这些困难。

问题

在我看来,这里最好的解决方案是异步返回数据,一旦解决,同步导出。

如何在这种情况下完成这样的任务?

这里的胜利将是,

  1. 在 /secrets/index.js 中提出请求
  2. 在同一个文件中构建 secrets 对象
  3. 将机密导出为对象,无需异步函数即可轻松导入应用程序的其他任何位置。

我想如何使用它的示例

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;

【问题讨论】:

    标签: javascript node.js


    【解决方案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; };
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2016-02-22
      • 1970-01-01
      • 1970-01-01
      • 2013-05-11
      • 1970-01-01
      相关资源
      最近更新 更多