Javascript "TypeError: cancelled" error when calling "fetch" on iOS
I'm logging JS client errors using Sentry and there's a lot of TypeError: cancelled
errors. It's only occurring on iOS. I can't find anything on Google. Is this a native Javascript error or something else? What does it mean?
I also get similar errors in other languages, such as 취소됨
, Abgebrochen
, and cancelado
. This tells me that the error isn't raised by my code.
Frustrating right?
Recently our team encountered this same error. Here is what was happening in our case. When the page loads, the refresh button changes to cross button, now if some api request is in progress during this page loading time and the user click this cross button, then iOS chrome/safari throws this error. For the same situation Firefox browser throws TypeError: NetworkError when attempting to fetch resource
and chrome browser throughs TypeError: Failed to fetch
.
This is not really an issue which we should be worried about, and so we decided to make sentry ignore this error by using the ignoreErrors attribute of sentry.
Sentry.init({
dsn: "sentry_dsn",
ignoreErrors: [
'TypeError: Failed to fetch',
'TypeError: NetworkError when attempting to fetch resource.',
'TypeError: Cancelled'
],
});
Note:
Failed to fetch is also generated by CORS errors, please be mindful of that too.
Also we decided to ignore errors with statusCode in between 400 to 426 using the beforeSend callback of sentry.
We spent days trying to find this error. Hope this helps somebody.
Thank you
Also, This was originally written here: https://forum.sentry.io/t/typeerror-failed-to-fetch-reported-over-and-overe/8447/2
If you are using the fetch
API, it could be a problem with AbortController
and AbortSignal
in iOS 11.1-12, which would only be firing when someone tries to abort a fetch request (which is why it wouldn't necessarily affect all iOS users, explaining the inconsistency).
To elaborate, iOS 11.1-12 defines AbortController
and AbortSignal
in the DOM, but they are a stub - see here. So if you try to abort a fetch request in iOS in <= 12, the request will not abort and would likely throw some sort of error.
Given it is a TypeError
rather than an AbortError
it would seem likely that the issue is with the AbortController
not being properly/fully defined.
EDIT:
Further reading also seems to indicate that failed fetches
in iOS throw TypeError
errors, even for things like blocked fetches. As mentioned above, the issue could be with any ad-blockers installed (say, on a jailbroken iPhone) or a CORS
issue, and iOS would then throw TypeError
- Webkit BugZilla discussion. As such, concentrating on the error type might lead you down the wrong path.
According to the WHATWG standard: https://html.spec.whatwg.org/multipage/browsing-the-web.html#aborting-a-document-load, fetch requests will be cancelled when aborting the document (close or navigate away). Apparently browsers are behaving differently so I made a tool to test the browser behavior if that helps: https://request-cancellation-test.vercel.app (code).
Here're the test results for common browsers:
Field | Chrome 95 | Safari 14 | iOS Safari 14 | Firefox 94 |
---|---|---|---|---|
.toString() |
TypeError: Failed to fetch |
TypeError: cancelled |
TypeError: cancelled |
TypeError: NetworkError when attempting to fetch resource. |
.name |
TypeError |
TypeError |
TypeError |
TypeError |
.message |
Failed to fetch |
cancelled |
cancelled |
NetworkError when attempting to fetch resource. |
.stack |
TypeError: Failed to fetch at ... |
Here is another possibility for idiots like myself who did this ...
<form action="blah">
<button onClick="javascript:submitForm();">Do It</button>
</form>
function submitForm() {
fetch('https://api.mailerlite.com/api/v2/groups/group_name/subscribers', options)
.then(response => response.json())
.then(response => console.log(response))
.catch(err => console.error(err));
}
}
This will give the dreaded TypeError: cancelled
It's because its breaking off the connection and trying to do the post of the actual form you defined (which wasn't even going anywhere)
SOLUTION:
<form action="blah">
<button onClick="javascript:submitForm(event);">Do It</button>
</form>
function submitForm(event) {
event.preventDefault()
fetch('https://api.mailerlite.com/api/v2/groups/group_name/subscribers', options)
.then(response => response.json())
.then(response => console.log(response))
.catch(err => console.error(err));
}
}
This passes the event thru to the function where you can cancel the Form submission (using preventDefault) and then get on with your fetch.
Very simple stuff, but very easy to forget and have you pulling your hair out, especially coupled with that Fantastically Descriptive and Useful error message.