调用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。你遇到的情况,本质是请求格式严重错误,导致浏览器提前终止请求,将其判定为网络错误:
- 手动设置的
Content-Length与实际发送的Body长度不匹配,服务器拒绝请求后,浏览器将其识别为异常。 - 用
FormData构造的Body格式与Google Drive要求的multipart/related完全不兼容,触发了浏览器的请求解析错误。
核心代码问题
- Body构造错误:Google Drive的
multipart上传要求使用multipart/related格式,而你用了FormData(对应multipart/form-data格式),两者结构完全不同,这是400错误的直接原因。 - Content-Length计算错误:手动计算的长度仅包含两个Blob的大小,忽略了
FormData自动添加的分隔符、头部等内容,长度不匹配导致服务器拒绝请求。 - 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
相关产品推荐
相关产品推荐

