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?
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)
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);
}
});
For posterity, the reason you're not seeing any details and the reason this goes directly to your catch
block is indeed a CORS error.
The API does actually support CORS, at least for preflight and successful requests...
curl -v \
-X OPTIONS \
-H "Origin: https://example.com" \
-H "Access-Control-Request-Method: POST" \
-H "Access-Control-Request-Headers: authrorization,content-type,content-length" \
"https://www.googleapis.com/upload/drive/v3/files?uploadType=multipart"
< HTTP/2 200
< access-control-allow-headers: authrorization,content-type,content-length
< access-control-allow-methods: DELETE,GET,HEAD,OPTIONS,PATCH,POST,PUT
< access-control-allow-origin: https://example.com
The problem is one you'll see in many implementations where CORS response headers are not added for some or all unsuccessful requests. In this particular case, your incorrect content-type
header handling results in a 400 Bad Request response which is missing the Access-Control-Allow-Origin
header so your client-side code has no access and fetch()
fails with a TypeError
.
I would not be surprised if there wasn't another error present in your Console. Something like
Access to fetch at
'https://www.googleapis.com/upload/drive/v3/files?uploadType=multipart'
from origin'https://example.com
has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource. If an opaque response serves your needs, set the request's mode to 'no-cors' to fetch the resource with CORS disabled.POST
https://www.googleapis.com/upload/drive/v3/files?uploadType=multipart
net::ERR_FAILED 400 (Bad Request)