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

调用Google Drive API时Fetch抛出TypeError,网络控制台显示400错误

问题:Fetch调用Google Drive API返回400错误却抛出TypeError: Failed to Fetch

在浏览器环境中使用Fetch API调用Google Drive REST API上传文件时,遇到异常:

TypeError: Failed to fetch

查阅资料后怀疑是CORS问题,但无法准确定位。奇怪的是,Chrome开发者工具的网络标签显示实际请求返回400错误,但Fetch调用并未返回该响应,而是直接抛出异常,导致无法获取错误详情,只能通过网络工具才发现是400状态码。

请问为何这个400错误会被“吞掉”,应该如何捕获它?

补充信息

Fetch封装代码

async send() {
  this._build();
  return await fetch(this._parameterizedUrl, this._options);
}

调用代码

const request = Request.post(uploadUrl);
request.setContentType(contentType);
request.setBody(body);
request.setAccessToken(accessToken);
request.addHeader('Content-Length', contentLength);
const response = await request.send();

异常栈

TypeError: Failed to fetch
    at Request.send (api.js:240:1)

完整复现代码

document.addEventListener('DOMContentLoaded', async () => {
  
  const url = 'https://www.googleapis.com/upload/drive/v3/files?uploadType=multipart';
  const options = {};
  const authToken = '<Add Auth Token>';
  const boundary = 'boundary-' + Math.random().toString(36).substring(2);
  const contentType = `multipart/related; boundary=${boundary}`;

  // Body part meta data for the filename
  const meta = {
    name: 'test.json'
  };

  // Body part for media (the file)
  const data = {
    foo: "bar",
    bar: "baz"
  }

  const metaBlob = new Blob([JSON.stringify(meta)], {
    type: 'application/json',
  });

  const mediaBlob = new Blob([JSON.stringify(data)], {
    type: 'application/json',
  });

  const body = new FormData();
  body.append('metadata', metaBlob, "metadata");
  body.append('media', mediaBlob, name);
  let contentLength = metaBlob.size + mediaBlob.size;

  const headers = new Headers();
  headers.append('Authorization', `Bearer ${authToken}`);
  headers.append('Content-Type', contentType);
  headers.append('Content-Length', contentLength);

  options['method'] = 'POST';
  options['headers'] = headers;
  options['body'] = body;

  try {
    const response = await fetch(url, options);
    console.log(response);
  } catch (err) {
    console.error(err);
  }
});

分析与解决方案

为什么400错误会被当成TypeError抛出?

通常Fetch仅在网络层面失败(比如连接中断、DNS解析失败)时才会抛出TypeError。你遇到的情况,本质是请求格式严重错误,导致浏览器提前终止请求,将其判定为网络错误:

  1. 手动设置的Content-Length与实际发送的Body长度不匹配,服务器拒绝请求后,浏览器将其识别为异常。
  2. 用FormData构造的Body格式与Google Drive要求的multipart/related完全不兼容,触发了浏览器的请求解析错误。

核心代码问题

  1. Body构造错误:Google Drive的multipart上传要求使用multipart/related格式,而你用了FormData(对应multipart/form-data格式),两者结构完全不同,这是400错误的直接原因。
  2. Content-Length计算错误:手动计算的长度仅包含两个Blob的大小,忽略了FormData自动添加的分隔符、头部等内容,长度不匹配导致服务器拒绝请求。
  3. Content-Type冲突:FormData作为Body时,浏览器会自动设置multipart/form-data类型及对应的boundary,你手动设置的multipart/related与之冲突,破坏了请求格式。

修复步骤

1. 正确构造multipart/related格式的Body

不要使用FormData,直接拼接符合要求的字符串并转为Blob:

const boundary = 'boundary-' + Math.random().toString(36).substring(2);
// 构造元数据部分
const metaPart = `--${boundary}\r\nContent-Type: application/json\r\n\r\n${JSON.stringify(meta)}\r\n`;
// 构造文件内容部分
const mediaPart = `--${boundary}\r\nContent-Type: application/json\r\n\r\n${JSON.stringify(data)}\r\n--${boundary}--\r\n`;
// 合并为完整的请求体Blob
const bodyBlob = new Blob([metaPart, mediaPart], { type: `multipart/related; boundary=${boundary}` });

2. 移除错误的请求头设置

  • 无需手动设置Content-Length,浏览器会自动计算并设置正确的值。
  • 仅保留Authorization和正确的multipart/related类型的Content-Type。

3. 正确捕获所有错误响应

Fetch不会对4xx/5xx状态码自动抛出异常,需要手动检查response.ok状态,获取错误详情:

try {
  const response = await fetch(url, options);
  if (!response.ok) {
    // 解析API返回的错误详情
    const errorDetails = await response.json();
    console.error('API错误:', response.status, errorDetails);
    throw new Error(`${response.status}: ${errorDetails.error.message}`);
  }
  const result = await response.json();
  console.log('上传成功:', result);
} catch (err) {
  console.error('请求失败:', err);
}

完整修正后的代码

document.addEventListener('DOMContentLoaded', async () => {
  
  const url = 'https://www.googleapis.com/upload/drive/v3/files?uploadType=multipart';
  const options = {};
  const authToken = '<Add Auth Token>';
  const boundary = 'boundary-' + Math.random().toString(36).substring(2);
  const contentType = `multipart/related; boundary=${boundary}`;

  const meta = {
    name: 'test.json'
  };

  const data = {
    foo: "bar",
    bar: "baz"
  }

  // 正确构造multipart/related格式的请求体
  const metaPart = `--${boundary}\r\nContent-Type: application/json\r\n\r\n${JSON.stringify(meta)}\r\n`;
  const mediaPart = `--${boundary}\r\nContent-Type: application/json\r\n\r\n${JSON.stringify(data)}\r\n--${boundary}--\r\n`;
  const bodyBlob = new Blob([metaPart, mediaPart], { type: contentType });

  const headers = new Headers();
  headers.append('Authorization', `Bearer ${authToken}`);
  headers.append('Content-Type', contentType);

  options['method'] = 'POST';
  options['headers'] = headers;
  options['body'] = bodyBlob;

  try {
    const response = await fetch(url, options);
    if (!response.ok) {
      const errorDetails = await response.json();
      console.error('API错误详情:', errorDetails);
      throw new Error(`${response.status} ${response.statusText}: ${errorDetails.error.message}`);
    }
    const result = await response.json();
    console.log('上传成功:', result);
  } catch (err) {
    console.error('错误:', err);
  }
});

内容的提问来源于stack exchange,提问作者Jason Polites

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.17 09:59:54