繁体   English   中英

Node.js返回异步数据再同步导出

[英]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 中,我将无法导出它,因此当我需要数据库连接时,它可以在我的应用程序中的任何地方使用。 类似地,我的应用程序中有许多功能需要访问秘密数据。 如果我要遍历将我的所有代码包装在异步函数中的应用程序,它将继续导致更多这些困难。

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

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

这里的胜利是,

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

我想如何使用它的示例

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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM