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?

enter image description here

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.