Returning asynchronous data then exporting it synchronously in Node.js

后端 未结 2 1934
清歌不尽
清歌不尽 2021-01-01 04:34

Background

I am returning data from AWS Secrets Manager and using the aws-sdk to do so. Earlier I asked a question about how to correct

2条回答
  •  萌比男神i
    2021-01-01 05:04

    Because the needed data is gotten asynchronously, there's no way around making everything that depends on it (somehow) asynchronous as well. With asynchronicity involved, one possibility is to usually export functions that can be called on demand, rather than exporting objects:

    • an object that depends on the asynchronous data can't be meaningfully exported before the data comes back
    • if you export functions rather than objects, you can ensure that control flow starts from your single entry point and heads downstream, rather than every module initializing itself at once (which can be problematic when some modules depend on others to be initialized properly, as you're seeing)

    On another note, note that if you have a single Promise that needs to resolve, it's probably easier to call .then on it than use an async function. For example, rather than

    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
      }
    })();
    

    you might consider:

    const promise = require('../secrets');
    
    promise
      .then((secrets) => {
        // use secrets here
      })
      .catch((err) => {
        // handle errors
      });
    

    It's less wordy and probably easier to make sense of at a glance - better than a self-invoking async IIFE. IMO, the place to use await is when you have multiple Promises that need to resolve, and chaining .thens and returned Promises together gets too ugly.

    A module that depends on secrets to perform has to, in its code, have something that effectively waits for secrets to be populated. Although being able to use your const secrets = require('../secrets'); in your lower code example would be nice, it just isn't possible like that. You can export a function that takes secrets as a parameter rather than as a require, and then (synchronously!) return the instantiated 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;
    

    Then, to use it in another module, once the secrets are created, call makePool with the secrets, and then use / pass around the returned pool:

    const secretsProm = require('../secrets');
    const makePool = require('./makePool');
    secretsProm.then((secrets) => {
      const pool = makePool(secrets);
      doSomethingWithPool(pool);
    })
    .catch((err) => {
      // handle errors
    });
    

    Note that the doSomethingWithPool function can be completely synchronous, as is makePool - the asynchronous nature of secrets, once handled with .then in one module, does not have to be dealt with asynchronously anywhere else, as long as other modules export functions, rather than objects.

提交回复
热议问题