使用Promise链式调用S3操作时出现“Cannot read property 'push' of undefined”错误
我之前也碰到过类似的棘手问题,结合你的代码和报错信息,这个问题大概率是循环变量作用域冲突+异步操作上下文混乱导致的,下面是具体的分析和修复方案:
核心问题定位
你的报错来自AWS SDK内部的event_listeners.js,提示dataListeners数组未定义——这通常是因为异步请求的上下文被意外覆盖了。看你的代码,循环里用了var record:var是函数级作用域,在async循环中,所有迭代会共享同一个record变量。当await暂停当前迭代时,下一次迭代会修改record的值,导致之前未完成的S3请求绑定到了错误的上下文,最终触发SDK内部的数组未初始化错误。
另外,你代码里多层嵌套的await写法(比如Body: (await generateHTML( (await s3.getObject(...).promise()).Body.toString('utf-8'), ...)))也会加重上下文混乱的概率,让异步操作的执行逻辑变得不可预期。
修复步骤
1. 替换循环变量为const(最关键)
把for (var record of event.Records)改成for (const record of event.Records)。const是块级作用域,每个迭代都会创建独立的record变量,彻底避免异步操作中变量被覆盖的问题。
2. 拆分深层嵌套的await
将多层嵌套的异步操作拆分为单独的步骤,既能提升代码可读性,也能避免上下文冲突:
exports.handler = async (event) => { for (const record of event.Records) { // 快速过滤不需要处理的对象 if (!record.s3.object.key.includes(".") || record.s3.object.key === "website/") { continue; } try { // 1. 获取源S3对象的原始HTML内容 const sourceObject = await s3.getObject({ Bucket: record.s3.bucket.name, Key: record.s3.object.key }).promise(); const rawHtml = sourceObject.Body.toString('utf-8'); // 2. 调用HTML处理函数 const processedHtml = await generateHTML(rawHtml, record.s3.bucket.name); // 3. 上传处理后的HTML到目标桶 await s3.putObject({ Body: processedHtml, Bucket: DestBucket, Key: record.s3.object.key.slice("website/".length), ACL: 'public-read', ContentType: 'text/html' }).promise(); } catch (error) { // 添加错误日志,方便定位特定失败的对象 console.error(`处理对象 ${record.s3.object.key} 失败:`, error); // 可以根据业务需求选择抛出错误终止,或者继续处理其他对象 throw error; } } return { statusCode: 200, body: "所有对象处理完成" }; };
3. 额外检查项
- 确认
generateHTML的异步实现:确保这个函数始终返回有效的Promise,不会在某些场景下返回undefined或者抛出未捕获的错误——这类问题也可能间接导致SDK的上下文异常。 - AWS SDK版本兼容性:Node.js 12环境中Lambda自带的AWS SDK版本和Node.js 10的有差异,如果依赖特定SDK特性,可以在
package.json中显式指定SDK版本(比如"aws-sdk": "^2.1000.0"),然后打包部署。
为什么Node.js 10下能正常运行?
Node.js 10对async/await的处理逻辑和Node.js 12有细微差异,加上之前的回调模式下,闭包抵消了var的作用域问题,所以这个错误没有暴露出来。切换到纯async/await模式后,var的作用域缺陷就被放大了。
内容的提问来源于stack exchange,提问作者HOK

