使用Google Apps Script上传PDF到GCS时遇JavaScript运行时意外退出错误
Google Apps Script上传28MB PDF到GCS时出现「JavaScript runtime exited unexpectedly」错误
问题描述
尝试用Google Apps Script把28MB的PDF文件上传到Google Cloud Storage(GCS)时,持续收到「The JavaScript runtime exited unexpectedly」错误,尽管文件大小在UrlFetch调用的50MB配额范围内。
相关代码如下:
function uploadToGCS(fileBlob, index) { var service = getService(); if (!service.hasAccess()) { Logger.log("Please authorize %s", service.getAuthorizationUrl()); return; } var sheet = SpreadsheetApp.openById('sheetId').getSheets()[0] var fileName = sheet.getRange(params.CLIENT_FILE_COLUMN + index).getValue() fileName = fileName.replace(/,|&|\//gi, "-"); var sheetLinkCellValue = sheet.getRange(params.SHEET_LINK_COLUMN + index).getValue().toString().split('/') var txtFileBlob = Utilities.newBlob(sheetLinkCellValue[5]); txtFileBlob.setName(fileName); console.log("Uploading to text Bucket"); var txtUrl = 'https://www.googleapis.com/upload/storage/v1/b/BUCKET/o?uploadType=media&name=FILE' .replace("BUCKET", params.TXT_BUCKET_NAME) .replace("FILE", fileName + '.txt'); var txtResponse = UrlFetchApp.fetch(txtUrl, { method: "POST", contentLength: txtFileBlob.getBytes().length, contentType: 'application/txt', payload: txtFileBlob.getBytes(), headers: { Authorization: 'Bearer ' + service.getAccessToken() } }); var txtResult = JSON.parse(txtResponse.getContentText()); Logger.log(txtResult) var pdfUrl = 'https://www.googleapis.com/upload/storage/v1/b/BUCKET/o?uploadType=media&name=FILE' .replace("BUCKET", params.PDF_BUCKET_NAME) .replace("FILE", fileName + '.pdf'); var bytes = fileBlob.getBytes(); Logger.log('URL fetched successfully'); // 脚本在此处报错 var pdfResponse = UrlFetchApp.fetch(pdfUrl, { method: "POST", contentLength: bytes.length, contentType: fileBlob.getContentType(), payload: bytes, headers: { Authorization: 'Bearer ' + service.getAccessToken() } }); console.log("Uploaded to PDF Bucket!!!!"); var pdfResult = JSON.parse(pdfResponse.getContentText()); console.log(pdfResult) }
潜在原因分析
- 内存溢出:Apps Script运行时的内存配额远小于UrlFetch的文件大小限制(实际可用内存通常更紧张)。调用
fileBlob.getBytes()会把整个28MB的PDF加载成字节数组,加上脚本中其他变量的内存占用,容易触发内存耗尽,导致runtime意外退出。 - 冗余字节转换:UrlFetchApp支持直接传入Blob作为
payload,无需手动转成字节数组,额外的转换过程会增加内存开销。 - 缺乏错误捕获:当前代码没有try-catch块,无法捕获具体错误细节(比如内存不足、请求超时等),只能看到模糊的runtime退出提示。
- 单块上传稳定性差:虽然28MB没到UrlFetch的50MB上限,但单块上传大文件时,网络波动或服务端限制都可能导致请求失败,进而触发runtime异常。
解决方案建议
1. 直接传入Blob避免字节转换
修改PDF上传的请求配置,直接使用fileBlob作为payload,不用调用getBytes(),同时移除手动设置的contentLength(UrlFetch会自动处理Blob的长度和类型):
// 优化后的PDF上传代码 try { var pdfResponse = UrlFetchApp.fetch(pdfUrl, { method: "POST", contentType: fileBlob.getContentType(), payload: fileBlob, headers: { Authorization: 'Bearer ' + service.getAccessToken() } }); console.log("Uploaded to PDF Bucket!!!!"); var pdfResult = JSON.parse(pdfResponse.getContentText()); console.log(pdfResult); } catch (e) { console.error("PDF上传失败:", e.toString()); Logger.log("错误详情:", e); }
同样,TXT文件的上传也可以做类似优化,避免重复调用getBytes():
var txtResponse = UrlFetchApp.fetch(txtUrl, { method: "POST", contentType: 'text/plain', // 修正为标准contentType值 payload: txtFileBlob, headers: { Authorization: 'Bearer ' + service.getAccessToken() } });
2. 添加错误捕获机制
用try-catch包裹上传逻辑,获取具体错误信息,方便排查问题。
3. 改用可恢复上传(Resumable Upload)
对于大文件,GCS的可恢复上传更稳定,它会把文件分成小块上传,降低单次请求的内存压力。示例逻辑如下:
// 创建可恢复上传会话 var sessionUrl = UrlFetchApp.fetch('https://www.googleapis.com/upload/storage/v1/b/BUCKET/o?uploadType=resumable', { method: "POST", contentType: 'application/json', payload: JSON.stringify({name: fileName + '.pdf'}), headers: { Authorization: 'Bearer ' + service.getAccessToken(), 'X-Upload-Content-Type': fileBlob.getContentType(), 'X-Upload-Content-Length': fileBlob.getSize() } }).getHeaders()['Location']; // 分块上传(示例:按10MB分块) var chunkSize = 10 * 1024 * 1024; var fileSize = fileBlob.getSize(); var start = 0; while (start < fileSize) { var end = Math.min(start + chunkSize, fileSize) - 1; var chunk = fileBlob.getBytes(start, end - start + 1); var response = UrlFetchApp.fetch(sessionUrl, { method: "PUT", contentType: fileBlob.getContentType(), headers: { Authorization: 'Bearer ' + service.getAccessToken(), 'Content-Range': `bytes ${start}-${end}/${fileSize}` }, payload: chunk }); start = end + 1; }
4. 优化内存使用
- 避免同时加载多个大文件(比如不要同时保留TXT和PDF的字节数组)
- 及时释放不再使用的变量(比如上传后将
bytes设为null)
内容的提问来源于stack exchange,提问作者Orabi
相关产品推荐
相关产品推荐

