Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Timeout not throwing error with status TIMEOUT_ERROR #2915

Open
PopJoestar opened this issue Nov 16, 2022 · 16 comments · May be fixed by #4628
Open

Timeout not throwing error with status TIMEOUT_ERROR #2915

PopJoestar opened this issue Nov 16, 2022 · 16 comments · May be fixed by #4628
Labels
bug Something isn't working rtk-query
Milestone

Comments

@PopJoestar
Copy link

PopJoestar commented Nov 16, 2022

Hey, PR #2401 shows that fetchBaseQuery should throw a TIMEOUT_ERROR when time out delay is reached. However, I got an error without status {"message": "Aborted", "name": "AbortError"}. I'm using
"@reduxjs/toolkit": "^1.9.0" with "react-native": "0.70.3", "react": "18.1.0". Here is my base API:

export const BaseApi = createApi({
  reducerPath: 'baseApi',
  baseQuery: fetchBaseQuery({
    baseUrl: Config.BASE_URL,
    prepareHeaders: headers => {
      const token = Preferences.getToken();
      headers.set('authorization', `Bearer ${token}`);

      return headers;
    },
    timeout: 1,
  }),
  tagTypes: ['Auth', 'Notification', 'Restaurant'],
  endpoints: () => ({}),
});
@phryneas
Copy link
Member

Hmm, according to our tests at

expect(result?.error).toEqual({
status: 'TIMEOUT_ERROR',
error: 'AbortError: The user aborted a request.',
})

you should be getting

{
        status: 'TIMEOUT_ERROR',
        error: 'AbortError: The user aborted a request.',
      }

Can you create a small reproduction?

@PopJoestar
Copy link
Author

PopJoestar commented Nov 16, 2022

I will do a small reproduction as soon as I am free, but I think, the reason might be that fetch in React Native Android/IOS is a little bit different from fetch in a browser

@mjwvb
Copy link

mjwvb commented Nov 17, 2022

I have the same problem.

When I wrap fetchBaseQuery, I indeed get { status: 'TIMEOUT_ERROR', error: 'AbortError: The user aborted a request.' }.
However in the react components, useQuery().error returns { name: 'AbortError', message: 'Aborted' }

Edit: I'm using a web browser, so no react native

@phryneas
Copy link
Member

That seems very weird. Could one of you provide a small reproduction for that?

@mjwvb
Copy link

mjwvb commented Nov 17, 2022

This should reproduce the problem:

state/testApi.js

import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react';

export const testApi = createApi({
    reducerPath: 'test',
    baseQuery: fetchBaseQuery({ baseUrl: 'http://google.nl', timeout: 1 }),
    endpoints: (builder) => ({
        getContent: builder.query({ query: () => 'test' }),
    }),
});

// Add reducer/middleware to the state store

React component:

import testApi from 'state/testApi';

const App = (props) => {
    const queryState = testApi.endpoints.getContent.useQuery();
    console.log(queryState.error); // {name: 'AbortError', message: 'Aborted'}
}

@markerikson markerikson added this to the 1.9.x milestone Jan 28, 2023
@markerikson markerikson added the bug Something isn't working label Jan 28, 2023
@aaronfg
Copy link

aaronfg commented Jun 28, 2023

Just came across this bug myself using React Native.

The transformErrorResponse on the endpoint shows the "TIMEOUT_ERROR" but the error that actually makes it to the useQuery hook is that "AbortError" / "Aborted" one.

I also find it weird that I the case of a timeout aborting the action, even though the error is seen in the transformErrorResponse, any error returned there will not be used. The "AbortError" will ALWAYS be the error returned.

@phryneas
Copy link
Member

I just tried it with the reproduction code above, but it works for me - please check out this CodeSandbox as a starter and see if you can give me a full working reproduction:

https://codesandbox.io/s/hungry-ardinghelli-ysjxy2?file=/src/App.tsx

@ifigueroap
Copy link

I'm working on an Ionic React project, and after adding a timeout to fetchBaseQuery, I'm also getting {name: 'AbortError', message: 'Aborted'}. Although that doesn't particularly bothers me, I'd like to ask what is the proper way of catching and handling this error. We are already using an error boundary, but the exception seems to escape it.

This is the part of our code that, I think, should capture the error:

const { showBoundary } = useErrorBoundary();

  const {
    data,
    isError,
    error,
  } = TheAPI.useGetQuery(lastUpdateId);

  useEffect(() => {
    if (isError) {
      showBoundary(error);
    }
  }, [isAlertLocationsDataError, alertLocationsDataError]);

How should we catch the timeout error in user code?
Thanks

@phryneas
Copy link
Member

phryneas commented Jul 20, 2023

Honestly, with a full reproduction we maybe could just fix this.
Without it, this issue is going to stay an echo chamber of people who are too lazy to create a reproduction.

@ifigueroap
Copy link

Honestly, with a full reproduction we maybe could just fix this. Without it, this issue is going to stay an echo chamber of people who are too lazy to create a reproduction.

Look I really like this library, and I'm very thankful for its existence. That being said, hastily calling your users "lazy" is just, let us say, unprofessional. It's ok if you need a reproduction repo, I get it. But it's not ok to call names.

@phryneas
Copy link
Member

phryneas commented Jul 20, 2023

Excuse me, my wording might have been slightly off, but it comes down to the same base message:
Without someone putting in the effort to reproduce this in a way that I can see this myself, further people asking about solutions in this thread will not help solve the problem. We leave these issues open in the hopes that someone comes in at some point and says "hey, I've created a reproduction", which helps us fix the problem.
Instead, I get emails essentially saying "I have this problem too" on a very regular basis, and every time I am reminded that I cannot help anyone with that. Honestly, that's extremely frustrating.
The only way we could avoid this would be to close and lock issues - and that doesn't seem like a good solution either.

So let me rephrase it and ask the same question I already asked twice above in this thread: Could you please create any kind of reproduction that actually shows this issue?

From the source code of fetchBaseQuery, I cannot think of any way this could ever occur, so I really need this reproduction:

let response,
timedOut = false,
timeoutId =
timeout &&
setTimeout(() => {
timedOut = true
api.abort()
}, timeout)
try {
response = await fetchFn(request)
} catch (e) {
return {
error: {
status: timedOut ? 'TIMEOUT_ERROR' : 'FETCH_ERROR',
error: String(e),
},
meta,
}
} finally {
if (timeoutId) clearTimeout(timeoutId)
}

The only way I could even remotely assume this bug could appear would be if you use some kind of polyfil that throws if the AbortSignal is aborted after the fetch finishes, or tries to "re-run" the fetch mutliple times for whatever reason. (And even that only in a near-impossible race condition, because we do clear the timeout.)

@ifigueroap
Copy link

Thank you for your reply. I understand the frustration you are mentioning, and I didn't mean to be too harsh in my response. So let's clear the air here :) Also, I'll try to create a minimal repo that reproduces the issue. Hopefully for next week.

Nevertheless, In the code you quoted, what happens if the timeout is really small, like 1 millisecond, will it raise the exception before entering the try-catch? Because in my case the error "disappears" when I increase the timeout value...

@phryneas
Copy link
Member

Thanks, it would be greatly appreciated!

what happens if the timeout is really small, like 1 millisecond, will it raise the exception before entering the try-catch

It would abort the AbortSignal, which would not do anything. The exception is thrown by fetch encountering an AbortSignal that is being aborted.

Try it out:

const controller = new AbortController()
controller.abort()

has no effect apart from setting controller.signal.aborted to true.

@ifigueroap
Copy link

ifigueroap commented Sep 11, 2023

It took some time, but here I go. I made a small reproduction repo https://github.com/ifigueroap/redux-toolkit-timeout-bug
Inside the TheAPI.ts file there is a timeout variable, if set to 10 it yields ABORT_ERROR in the code. If set to 1000, it will work without errors.

Please let me know whether you can reproduce the issue, or if you need any further assistance.

Best regards,

@andrejpavlovic
Copy link
Contributor

andrejpavlovic commented Sep 12, 2023

It seems that there is a race condition. When a fetch request timeout calls abort() it could be the case that TIMEOUT_ERROR is returned or AbortError.

So here abort() is called:

setTimeout(() => {
timedOut = true
api.abort()
}, timeout)

And here is the race condition:

finalAction = await Promise.race([
abortedPromise,
Promise.resolve(
payloadCreator(arg, {
dispatch,
getState,
extra,
requestId,
signal: abortController.signal,
abort,
rejectWithValue: ((

When api.abort() is called, either of the two promises in Promise.race above could resolve first.

@markerikson markerikson modified the milestones: 1.9.x, 2.x bugfixes Dec 6, 2023
@n-ii-ma
Copy link
Contributor

n-ii-ma commented Jan 20, 2024

Any update on this?

Still getting AbortError on React Native.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working rtk-query
Projects
None yet
8 participants