我的函数在lambda中的流动方式存在一些问题。我试图获取存储在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(event)。我认为这很奇怪,因为通常将它们声明为(事件,上下文,回调)。切换到更高版本(甚至没有异步的(事件))可以解决此问题。看来我的问题是将函数作为异步调用。这阻止了瀑布异步调用??有人可以详细说明吗?
答案 0 :(得分:2)
您的问题是您的处理程序被声明为async
函数,这将自动为您创建一个Promise,但是由于您根本没有等待,因此您的函数实质上是同步结束的。
有两种方法可以解决此问题,我们将介绍所有这些方法。
async
库旨在使用。async
库或回调,而应使用async
/ await
。resolve
/ reject
。在此解决方案中,您将删除async
关键字,并添加lambda传递给您的回调参数。只需调用它将结束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 ──┘
});
};
async
/ await
这里的想法是不使用回调样式,而是使用基于Promise的async
/ await
关键字。如果返回承诺,lambda将使用该承诺来处理lambda完成而不是回调。
如果您有一个带有async
关键字的函数,它将自动返回对您的代码透明的promise。
为此,我们需要修改您的代码以不再使用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 };
};
在这种方法中,我们仍在使用基于回调的异步库,但是我们的外部函数是基于承诺的。很好,但是在这种情况下,我们需要手动做出自己的承诺,并在瀑布处理程序中解决或拒绝它。
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 });
});
});
};
除了您遇到的回调与承诺的主要问题之外,还有一些我注意到的小问题:
大多数情况下,您应该使用const
而不是let
。唯一应该使用let
的情况是,如果您打算重新分配变量,则大多数时候不应该这样做。我想用一种不需要编写let
的代码的方式来挑战您,它将帮助您总体上改善代码。
在瀑布步骤之一中,您将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
来输入错误,则会将其解释为错误,并且会跳过瀑布的其余部分并直接完成处理程序传递该错误。
您需要了解的关于context.callbackWaitsForEmptyEventLoop
的信息是,即使您正确完成了该功能,以上述方法之一,您的lambda仍可能会挂起并最终超时而不是成功完成。根据此处的代码示例,您可能不必担心这一点,但是发生这种情况的原因是,如果您碰巧没有正确关闭某些内容,例如与数据库或websocket的持久连接或类似的内容, 。在执行lambda的开始时将此标志设置为false
会导致该进程退出,而不管任何使事件循环保持活动的状态,并且将迫使它们不合常规地关闭。
在以下情况下,您的lambda可以成功完成工作,甚至返回成功结果,但它将一直挂起直到超时,并报告为错误。甚至可以反复触发它,具体取决于触发方式。
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()
就可以解决问题,但是有时它并不清楚事件循环中正在徘徊的是什么,您只需要大锤类型的解决方案来关闭lambda,这就是{{1 }}是为了!
context.callbackWaitsForEmptyEventLoop = false
函数返回后,上面的代码将立即完成lambda,杀死所有连接或仍然存在于事件循环中的其他任何东西。
答案 1 :(得分:0)
您的函数在解析瀑布之前终止。也就是说,根本不执行异步调用。这就是为什么您看不到console.log
函数中有任何waterfall
调用,而仅看到在调用async.waterfall
之后立即被同步调用的原因。
不确定AWS Lambda对async.waterfall
的支持程度如何,但是由于原生支持promise并执行相同的功能(位置更少),因此您可以使用promises。您的代码如下所示:
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));