I am attempting to call the Google Drive REST API to upload a file from a browser context using the fetch
api, but am not able to progress past a weird error.
TypeError: Failed to fetch
Looking at other posts on the topic, some point to a CORS failure, but it’s pretty difficult to determine exactly what the error is from this message. The strange thing is, looking at the Network tab of the Chrome devtools tells a different story:
Now I’ll be the first to admit that there’s a (high?) chance I’m sending a malformed request, but it’s also pretty difficult to determine what about the request is malformed without any error message.
What I’m doubly confused by, is why the fetch
call is rejecting (throws error with await
), rather than resolving with a 400 response.
The code to make the call is about as basic as it gets:
await fetch(`https://www.googleapis.com/upload/drive/v3/files?uploadType=multipart`, options);
The options
object is pretty boiler-plate, and just sets things like an Authorization
header, Content-Type
, Content-Length
, as well as the body which (in this case) is multipart/related
So I neither get a graceful resolve
response with a message, nor do I get useful reject
outcome, rather just TypeError: Failed to fetch
If I were to rely on the rejection alone, I would have no idea this was actually a 400. It was only the Network tools that revealed this.
Anyone have a clue why this 400 error would be “swallowed”, and what I need to do to capture it?
Additional Edits
Further detail in response to comments. The code around the fetch looks like this:
async send() {
this._build();
return await fetch(this._parameterizedUrl, this._options);
}
This is contained in an object which just encapsulates the process of creating a request (called a Request
). So the this
pointer refers to the encapsulating object. this._parameterizedUrl
is just the value of the URL string already posted, and this._options
is the options object already mentioned. The call to build()
just assembles the values of the Request
object, mostly into the options
argument (but also supports URL parameters, not used in this case)
The code calling this looks like:
const request = Request.post(uploadUrl);
request.setContentType(contentType);
request.setBody(body);
request.setAccessToken(accessToken);
request.addHeader('Content-Length', contentLength);
const response = await request.send();
The Request.post(uploadUrl)
call is a shorthand to create my Request
object (this is my own encapsulation as mentioned)
This call will throw an error, which is caught at an earlier point in the stack. The next line of the trace from the Failed to fetch
error looks like this:
TypeError: Failed to fetch
at Request.send (api.js:240:1)
Full Reproduction
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);
}
});