Фон
Я возвращаю данные из 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, у меня проблемы с ее экспортом, поэтому его можно использовать в любом месте моего приложения, когда мне нужно подключение к базе данных. Аналогично, у меня есть много функций во всем приложении, которые нуждаются в доступе к секретным данным. Если бы мне пришлось пройти через приложение, заверяющее весь мой код в асинхронных функциях, это продолжало бы вызывать больше этих трудностей.
Вопрос
Мне кажется, лучшим решением здесь было бы возвращение данных асинхронно и после его разрешения экспортировать его синхронно.
Как я могу достичь такой задачи в этом сценарии?
Победа здесь будет,
Пример того, как я хотел бы использовать это
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. ИМО, место для использования в 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
в одном модуле, не нужно обрабатывать асинхронно в другом месте, если другие функции экспорта модулей, а не объекты.
Я бы предложил сделать все в 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; };