AWS Лямбда-функция потока - PullRequest
0 голосов
/ 03 декабря 2018

У меня есть некоторые проблемы с тем, как мои функции протекают в лямбде.Я пытаюсь получить значение, хранящееся в S3, увеличить его и вернуть обратно.Однако моя программа не работает так, как мне кажется.Я использую асинхронный водопад для запуска потока моих функций.

Вот мой код:

let AWS = require('aws-sdk');
let async = require('async');

let bucket = "MY_BUCKET";
let key = "MY_FILE.txt";

exports.handler = async (event) => {
    let s3 = new AWS.S3();
    async.waterfall([
        download,
        increment,
        upload
        ], function (err) {
            if (err) {
                console.error(err);
            } else {
                console.log("Increment successful");
            }

            console.log("test4");
            return null;
        }
    );
    console.log("test5");

    function download(next) {
        console.log("test");
        s3.getObject({
            Bucket: bucket,
            Key: key
        },
        next);
    }
    function increment(response, next) {
        console.log("test2");
        console.log(response.Body);
        let newID = parseInt(response.Body, 10) + 1;
        next(response.ContentType, newID);
    }
    function upload(contentType, data, next) {
        console.log("test3");
        s3.putObject({
            Bucket: bucket,
            Key: key,
            Body: data,
            ContentType: contentType
        },
        next);
    }
};

Я получаю только test и test5 в моем журнале.У меня сложилось впечатление, что после функции загрузки инкремент должен запускаться, если все в порядке, или должна выполняться функция обратного вызова в конце водопада, если произошла ошибка.Программа не выдает ошибку при выполнении и не выполняет ни одну из функций.

Может ли кто-нибудь подсказать мне, что мне не хватает в моем понимании?

РЕДАКТИРОВАТЬ:Так что, похоже, моя проблема была связана с моим объявлением функции.Шаблон по умолчанию объявил его как async (событие).Я думал, что это было странно, так как обычно они объявляются как (событие, контекст, обратный вызов).Переключение на более позднее (или даже просто (событие) без асинхронного) исправило это.Похоже, моя проблема в том, чтобы вызвать функцию как асинхронную.Это заблокировало водопад асинхронных звонков ??Кто-нибудь может уточнить это?

Ответы [ 2 ]

0 голосов
/ 06 декабря 2018

Ваша проблема в том, что ваш обработчик объявлен как функция async, которая автоматически создаст для вас обещание, но, поскольку вы вообще не ожидаете, ваша функция по существу завершается синхронно.

Естьнесколько способов решить эту проблему, и все мы рассмотрим.

  1. Не используйте обещания, используйте обратные вызовы, поскольку библиотека async предназначена для использования.
  2. Не используйте библиотеку async или обратные вызовы, вместо этого используйте async / await.
  3. Смешайте оба вместе и сделайте свое собственное обещание и 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 }
}

Вышеуказанное завершит лямбду, как только функция вернется, уничтожив все соединения или все, что еще осталось в цикле событий.

0 голосов
/ 04 декабря 2018

Ваша функция прекращается до разрешения водопада.То есть асинхронные вызовы вообще не выполняются.Вот почему вы не видите ни одного из вызовов console.log, которые есть у вас в функциях waterfall, а только тот, который вызывается синхронно, сразу после вызова на async.waterfall.

Не уверен, насколько хорошо async.waterfall поддерживается AWS Lambda, но, поскольку обещания изначально поддерживаются и выполняют те же функции (с меньшим количеством loc), вы можете вместо этого использовать обещания.Ваш код будет выглядеть примерно так:

module.exports.handler = (event,context) =>
    s3.getObject({
        Bucket: bucket,
        Key: key
    }).promise()
        .then(response => ({
            Body: parseInt(response.Body, 10) + 1,
            ContentType: response.contentType,
        }))
        .then(modifiedResponse => s3.putObject({
            Bucket: bucket,
            Key: key,
            Body: modifiedResponse.data,
            ContentType: modifiedResponse.contentType}).promise())
        .catch(err => console.error(err));
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...