AWS Lambda中Node.js finally块执行异常问题咨询
AWS Lambda中Node.js finally块执行异常问题解答
问题背景
使用Node/Express/TypeScript编写AWS Lambda API端点,将错误保存到DynamoDB的逻辑放在finally块中,伪代码如下:
async getById(req: Request, res: Response): Promise<void> { try { throw new Error("test error"); } catch (error) { console.error(error); res.status(500).send("Error occurred"); return; } finally { // 异步保存错误到DynamoDB saveErrorToDynamoDB(error); } }
请求流程:用户 -> CloudFront -> Api Gateway -> Lambda -> 应用 -> getByID方法(抛出错误) -> Dynamo DB
观察到异常现象:
- 第一次调用:抛出错误,DynamoDB无记录
- 第二次调用:抛出新错误,DynamoDB出现第一次的错误记录
- 第三次调用:抛出新错误,DynamoDB出现第一、二次的错误记录
以此类推,当前错误需等待下一次调用才会写入。
问题解答
1. 为什么第一次API调用时finally块的DynamoDB写入未执行?
这是Lambda执行模型与Node.js异步行为共同作用的结果:
- 你的
saveErrorToDynamoDB是异步操作,但finally块中没有用await等待它完成。 - 当
catch块调用res.send()并return后,Express会立即向API Gateway返回响应,Lambda runtime会判定async函数的Promise已完成,认为函数执行结束。 - Lambda默认不会等待Node.js事件循环清空就会冻结或终止执行容器,此时
finally里的异步写入任务还处于挂起状态,没有机会完成,所以第一次调用的错误不会写入DynamoDB。
2. 为什么后续API调用会触发前一次错误写入?
核心原因是Lambda的**容器复用(Warm Start)**机制:
- 第一次调用结束后,Lambda容器不会立即销毁,而是被冻结保存。容器内Node.js进程的事件循环中,挂起的
saveErrorToDynamoDB任务依然存在。 - 当第二次API调用进来时,Lambda会复用这个已冻结的容器,解冻后Node.js进程的事件循环会继续处理之前未完成的异步任务,所以第一次的错误写入操作会在此时完成,写入DynamoDB。
- 而第二次调用的
finally块异步任务又会因为同样的原因被挂起,等待下一次容器复用时才执行。
3. 存在finally块时,返回响应的逻辑与Node.js事件循环行为是怎样的?
- 返回响应逻辑:
catch块中调用res.send()后,Express会构建响应并发送给API Gateway,随后的return会让async函数的Promise状态变为resolved。Lambda runtime检测到Promise settled后,就会准备结束函数执行,不会等待后续异步操作完成。 - Node.js事件循环行为:Node.js是单线程事件驱动模型,异步任务会被放入事件队列,等待主线程空闲时执行。但Lambda默认配置
callbackWaitsForEmptyEventLoop为false,意味着Lambda runtime不会等待事件队列中的所有任务完成就会结束函数,只会在handler的Promise settled或callback被调用后终止。 - 对于async函数中的
finally块,它会在try/catch的Promise settled后执行,但如果finally里是未被await的异步任务,它只会被加入事件队列,无法在当前Lambda调用周期内得到执行,只能等到下一次容器复用时,事件循环恢复后才会处理。
内容的提问来源于stack exchange,提问作者TheSchithsification
相关产品推荐
相关产品推荐

