AWS Lambda: асинхронные вызовы внешнего обработчика (раздел инициализации, вызов lambda)

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