#asynchronous #async-await #aws-lambda #initialization #aws-secrets-manager
#асинхронный #асинхронный-ожидание #aws-lambda #инициализация #aws-secrets-manager
Вопрос:
Я хотел бы вызвать асинхронную функцию вне обработчика lambda с помощью следующего кода:
var client;
(async () => {
var result = await initSecrets("MyWebApi");
var secret = JSON.parse(result.Payload);
client= new MyWebApiClient(secret.API_KEY, secret.API_SECRET);
});
async function initSecrets(secretName) {
var input = {
"secretName" : secretName
};
var result = await lambda.invoke({
FunctionName: 'getSecrets',
InvocationType: "RequestResponse",
Payload: JSON.stringify(input)
}).promise();
return resu<
}
exports.handler = async function (event, context) {
var myReq = await client('Request');
console.log(myReq);
};
«Клиент» не инициализируется. Тот же код отлично работает, если выполняется внутри обработчика.
initSecrets содержит лямбда-вызов getSecrets(), который вызывает AWS SecretsManager
Есть ли у кого-нибудь идея, как асинхронные функции могут быть правильно вызваны для инициализации вне обработчика?
Большое вам спасибо за вашу поддержку.
Комментарии:
1. @Dennis Bauszus предлагает правильное решение, приведенное ниже. Вы можете получить свое обещание в разделе вне вашего лямбда-обработчика, а затем назначить его новой локальной переменной и «ожидать» для нее. Таким образом, он инициализируется только один раз, и ожидание удостоверится, что оно завершено.
Ответ №1:
Я столкнулся с аналогичной проблемой, пытаясь заставить next-js работать с aws-serverless-express.
Я исправил это, выполнив приведенное ниже (используя typescript, поэтому просто игнорируйте биты типа:any)
const appModule = require('./App');
let server: any = undefined;
appModule.then((expressApp: any) => {
server = createServer(expressApp, null, binaryMimeTypes);
});
function waitForServer(event: any, context: any){
setImmediate(() => {
if(!server){
waitForServer(event, context);
}else{
proxy(server, event, context);
}
});
}
exports.handler = (event: any, context: any) => {
if(server){
proxy(server, event, context);
}else{
waitForServer(event, context);
}
}
Итак, для вашего кода может быть что-то вроде
var client = undefined;
initSecrets("MyWebApi").then(result => {
var secret = JSON.parse(result.Payload);
client= new MyWebApiClient(secret.API_KEY, secret.API_SECRET)
})
function waitForClient(){
setImmediate(() => {
if(!client ){
waitForClient();
}else{
client('Request')
}
});
}
exports.handler = async function (event, context) {
if(client){
client('Request')
}else{
waitForClient(event, context);
}
};
Комментарии:
1. Большое тебе спасибо, Уилл, за это предложение. Это должно сработать. Тем временем я добавил var init = 0 в глобальном контексте для обнаружения холодных запусков.
2. Нет проблем, интересная идея определить холодные запуски по сравнению с предварительным запуском.
Ответ №2:
client
вызывается до его инициализации; client
переменная «экспортируется» (и вызывается) до того, как async
функция завершилась бы. Когда вы вызываете, await client()
клиент все равно будет undefined
.
отредактируйте, попробуйте что-то вроде этого
var client = async which => {
var result = await initSecrets("MyWebApi");
var secret = JSON.parse(result.Payload);
let api = new MyWebApiClient(secret.API_KEY, secret.API_SECRET);
return api(which) // assuming api class is returning a promise
}
async function initSecrets(secretName) {
var input = {
"secretName" : secretName
};
var result = await lambda.invoke({
FunctionName: 'getSecrets',
InvocationType: "RequestResponse",
Payload: JSON.stringify(input)
}).promise();
return resu<
}
exports.handler = async function (event, context) {
var myReq = await client('Request');
console.log(myReq);
};
Комментарии:
1. Большое тебе спасибо, Тобин, за твое объяснение. Итак, как я могу выполнить инициализацию клиента вне обработчика, один раз, во время создания модуля?
Ответ №3:
Это также можно решить с помощью async / await give Node v8
Вы можете загрузить свою конфигурацию в модуль следующим образом…
const fetch = require('node-fetch');
module.exports = async () => {
const config = await fetch('https://cdn.jsdelivr.net/gh/GEOLYTIX/public/z2.json');
return await config.json();
}
Затем объявите _config вне обработчика, потребовав / выполнив модуль конфигурации. Ваш обработчик должен быть асинхронной функцией. _config сначала будет обещанием, которого вы должны дождаться для преобразования в объект конфигурации.
const _config = require('./config')();
module.exports = async (req, res) => {
const config = await _config;
res.send(config);
}
Ответ №4:
В идеале вы хотите, чтобы ваш код инициализации выполнялся на этапе инициализации, а не на этапе вызова lambda, чтобы минимизировать время холодного запуска. Синхронный код на уровне модуля выполняется во время инициализации, и AWS недавно добавила поддержку ожидания верхнего уровня в node14 и более новых лямбдах: https://aws.amazon.com/blogs/compute/using-node-js-es-modules-and-top-level-await-in-aws-lambda / . Используя это, вы можете заставить фазу инициализации ожидать вашего кода асинхронной инициализации, используя ожидание верхнего уровня следующим образом:
const sleep = ms => new Promise(resolve => setTimeout(resolve, ms))
console.log("start init");
await sleep(1000);
console.log("end init");
export const handler = async (event) => {
return {
statusCode: 200,
body: JSON.stringify('Hello from Lambda!'),
};
};
Это отлично работает, если вы используете модули ES. Если по какой-либо причине вы застряли с использованием commonjs (например, потому, что ваши инструменты, такие как jest или ts-node, еще не полностью поддерживают модули ES), вы можете сделать свой модуль commonjs похожим на модуль es, заставив его экспортировать обещание, которое ожидает вашей инициализации, а не экспортировать объект. Вот так:
const sleep = ms => new Promise(resolve => setTimeout(resolve, ms))
const main = async () => {
console.log("start init");
await sleep(1000);
console.log("end init");
const handler = async (event) => {
return {
statusCode: 200,
body: JSON.stringify('Hello from Lambda!'),
};
};
return { handler };
};
# note we aren't exporting main here, but rather the result
# of calling main() which is a promise resolving to {handler}:
module.exports = main();