You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.30 05:40:39