Ваша проблема в том, что ваш обработчик объявлен как функция async
, которая автоматически создаст для вас обещание, но, поскольку вы вообще не ожидаете, ваша функция по существу завершается синхронно.
Естьнесколько способов решить эту проблему, и все мы рассмотрим.
- Не используйте обещания, используйте обратные вызовы, поскольку библиотека
async
предназначена для использования. - Не используйте библиотеку
async
или обратные вызовы, вместо этого используйте async
/ await
. - Смешайте оба вместе и сделайте свое собственное обещание и
resolve
/ reject
вручную.
1.Не используйте обещания
В этом решении вы удалите ключевое слово async
и добавите параметр обратного вызова, который lambda передает вам.Простое обращение к нему приведет к завершению лямбды, а передача ошибки приведет к сбою функции.
// Include the callback parameter ────┐
exports.handler = (event, context, callback) => {
const params =[
download,
increment,
upload
]
async.waterfall(params, (err) => {
// To end the lambda call the callback here ──────┐
if (err) return callback(err); // error case ──┤
callback({ ok: true }); // success case ──┘
});
};
2.Используйте async
/ await
Идея состоит в том, чтобы не использовать стиль обратного вызова, а вместо этого использовать ключевые слова на основе Promise async
/ await
.Если вы вернете обещание, лямбда будет использовать это обещание для обработки лямбда-завершения вместо обратного вызова.
Если у вас есть функция с ключевым словом async
, она автоматически вернет обещание, прозрачное для вашего кода.
Для этого нам нужно изменить ваш код, чтобы он больше не использовал библиотеку async
, а также сделать ваши другие функции асинхронными.
const AWS = require('aws-sdk');
const s3 = new AWS.S3();
const Bucket = "MY_BUCKET";
const Key = "MY_FILE.txt";
async function download() {
const params = {
Bucket,
Key
}
return s3.getObject(params).promise(); // You can await or return a promise
}
function increment(response) {
// This function is synchronous, no need for promises or callbacks
const { ContentType: contentType, Body } = response;
const newId = parseInt(Body, 10) + 1;
return { contentType, newId };
}
async function upload({ contentType: ContentType, newId: Body }) {
const params = {
Bucket,
Key,
Body,
ContentType
};
return s3.putObject(params).promise();
}
exports.handler = async (event) => {
const obj = await download(); // await the promise completion
const data = increment(obj); // call synchronously without await
await upload(data)
// The handlers promise will be resolved after the above are
// all completed, the return result will be the lambdas return value.
return { ok: true };
};
3.Смешайте обещания и обратные вызовы
В этом подходе мы все еще используем асинхронную библиотеку, которая основана на обратном вызове, но наша внешняя функция основана на обещании.Это нормально, но в этом сценарии нам нужно сделать собственное обещание вручную и разрешить или отклонить его в обработчике водопада.
exports.handler = async (event) => {
// In an async function you can either use one or more `await`'s or
// return a promise, or both.
return new Promise((resolve, reject) => {
const steps = [
download,
increment,
upload
];
async.waterfall(steps, function (err) {
// Instead of a callback we are calling resolve or reject
// given to us by the promise we are running in.
if (err) return reject(err);
resolve({ ok: true });
});
});
};
Разное
В дополнение к основной проблеме обратных вызовов противОбещания, с которыми вы столкнулись, у вас есть несколько незначительных проблем, которые я заметил:
Разное 1
Вы должны использовать const
вместо let
большую часть времени.Единственный раз, когда вы должны использовать let
, это если вы намереваетесь переназначить переменную, и большую часть времени вам не следует этого делать.Я хотел бы предложить вам способы написания кода, который никогда не требует let
, это поможет улучшить ваш код в целом.
Разное 2
У вас есть проблема на одном из этапов водопада, когдавы возвращаете response.ContentType
в качестве первого аргумента next
, это ошибка, потому что она будет интерпретирована как ошибка.Подпись для обратного вызова next(err, result)
, поэтому вы должны делать это в своих функциях увеличения и загрузки:
function increment(response, next) {
const { ContentType: contentType, Body: body } = response;
const newId = parseInt(body, 10) + 1;
next(null, { contentType, newId }); // pass null for err
}
function upload(result, next) {
const { contentType, newId } = result;
s3.putObject({
Bucket: bucket,
Key: key,
Body: newId,
ContentType: contentType
},
next);
}
Если вы не передаете null
или undefined
для ошибки при вызове следующего async
будет интерпретировать это как ошибку, пропустит оставшуюся часть водопада и перейдет к обработчику завершения, передавшему эту ошибку.
Разное 3
Что нужно знать о context.callbackWaitsForEmptyEventLoop
в том, что даже если вы правильно завершите функцию, одним из способов, описанных выше, ваша лямбда может все еще оставаться открытой и, в конечном итоге, превысить тайм-аут, а не успешно завершиться.Основываясь на приведенном здесь примере кода, вам, вероятно, не придется беспокоиться об этом, но причина, по которой это может произойти, заключается в том, что если у вас есть что-то, что не закрыто должным образом, например постоянное соединение с базой данных или веб-сокетом или что-то подобное,Установка этого флага в false
в начале вашего лямбда-выполнения приведет к тому, что процесс завершится независимо от чего-либо, поддерживающего цикл обработки событий, и заставит их закрыться некорректно.
В случае ниже ваша лямбда можетвыполнить работу успешно и даже вернуть результат успеха, но он будет висеть открытым до истечения времени ожидания и сообщаться как ошибка.Его даже можно повторно вызывать снова и снова в зависимости от того, как он запускается.
exports.handler = async (event) => {
const db = await connect()
await db.write(data)
// await db.close() // Whoops forgot to close my connection!
return { ok: true }
}
В этом случае простой вызов db.close()
решит проблему, но иногда не очевидно, что происходит в цикле событий.и вам просто нужно решение типа кувалды, чтобы закрыть лямбду, для этого context.callbackWaitsForEmptyEventLoop = false
!
exports.handler = async (event) => {
context.callbackWaitsForEmptyEventLoop = false
const db = await connect()
await db.write(data)
return { ok: true }
}
Вышеуказанное завершит лямбду, как только функция вернется, уничтожив все соединения или все, что еще осталось в цикле событий.