Возврат асинхронных данных и их синхронный экспорт в Node.js.

1

Фон

Я возвращаю данные из AWS Secrets Manager и использую aws-sdk для этого. Раньше я задавал вопрос о том, как правильно возвращать данные и экспортировать их, поскольку экспортируемый объект никогда не разрешал данные к моменту экспорта импорта в другое место. Это заставило меня получить кучу неопределенных.

После решения этой проблемы было определено, что способ справиться с этим состоит в том, чтобы обернуть функцию aws-sdk в обещание, а затем вызвать обещание в другом файле с ожиданием async. Это вызывает у меня проблемы.

пример

Если я запрошу и верну данные из 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 функцию самосинхронизации async, у меня проблемы с ее экспортом, поэтому его можно использовать в любом месте моего приложения, когда мне нужно подключение к базе данных. Аналогично, у меня есть много функций во всем приложении, которые нуждаются в доступе к секретным данным. Если бы мне пришлось пройти через приложение, заверяющее весь мой код в асинхронных функциях, это продолжало бы вызывать больше этих трудностей.

Вопрос

Мне кажется, лучшим решением здесь было бы возвращение данных асинхронно и после его разрешения экспортировать его синхронно.

Как я могу достичь такой задачи в этом сценарии?

Победа здесь будет,

  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;
Теги:

2 ответа

2
Лучший ответ

Поскольку необходимые данные получают асинхронно, нет никакого способа сделать все, что от него зависит (как-то), асинхронно. С учетом асинхронности одна из возможностей заключается в том, чтобы обычно экспортировать функции, которые можно вызывать по требованию, а не экспортировать объекты:

  • объект, который зависит от асинхронных данных, не может быть значимо экспортирован до того, как данные вернутся
  • если вы экспортируете функции, а не объекты, вы можете обеспечить, чтобы поток управления начинался с вашей единственной точки входа и направлялся вниз по течению, а не каждый модуль, инициализирующийся сразу (что может быть проблематично, когда некоторые модули зависят от других, которые должны быть инициализированы должным образом, поскольку вы видеть)

В другой заметке обратите внимание, что если у вас есть одна 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. ИМО, место для использования в await, когда у вас есть несколько Promises, которые необходимо решить, и сцепление .then и вернулись 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 будут созданы, вызовите makePool с secrets, а затем используйте/передайте возвращаемый 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 в одном модуле, не нужно обрабатывать асинхронно в другом месте, если другие функции экспорта модулей, а не объекты.

  • 0
    CertainPerformance снова поражает ...: | Единственное, что с этим кодом, то каждый раз, когда вы вызываете его, вы создаете новый экземпляр пула. Итак ... вы не сможете легко получить доступ к одному и тому же экземпляру в разных файлах. Мой код противоположен. Он не может легко создавать новые экземпляры, но ссылается на один и тот же экземпляр в любом файле.
  • 0
    @TJBlackmanI на самом деле просто печатал, чтобы спросить об этом. Я поражен, что это причиняет мне столько горя. Я согласился с этим, потому что он имел немного больше смысла для меня, исходя из моей конкретной проблемы. Я также ценю твою чертовщину.
Показать ещё 4 комментария
1

Я бы предложил сделать все в 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

Вы увидите, что когда экспортируется только значение (object, array, w/e), оно экспортируется как есть, когда выполняется экспорт - почти всегда до завершения вызова API.

BUT. Если вы экспортируете функцию, возвращающую значение (объект, массив, 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; };

Ещё вопросы

Сообщество Overcoder
Наверх
Меню