-
-
Notifications
You must be signed in to change notification settings - Fork 32
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
Retry API #50
Comments
In my opinion, we also could provide a set of common presets for timeout — e.g. exponential timeout is pretty common. So, we can provide |
Related to #51 |
Should it be in core or in the separate add on? |
I feel a bit weird about this api What if i apply // page-a.model.ts
retry({
source: locationQuery,
filter: isNetworkError,
timeout: (params, { retryNumber }) => retryNumber * 1000,
retries: 10,
})
// page-b.model.ts
retry({
source: locationQuery,
filter: isNetworkError,
timeout: (params, { retryNumber }) => retryNumber * 10,
retries: 2,
}) Which behavoiur will be active and when? Looks like it should not be separate logical operator and rather should be part of initial Query configuration, which is a bit less confusing 🤔 |
I've thought about it, let me explain a bit. Let's start with tree-shaking. What if you do not want to apply reties to your queries? It would be a quite tricky to make it tree-shakable, I assume something like this would work, but it has its own problems too 👇 const query = createQuery({
// ...
addOns: [
retry({
source: locationQuery,
timeout: (params, { retryNumber }) => retryNumber * 10,
retries: 2,
})
]
})
Ok, we can make an exception for retries and consider it as an essential part, so we are fallbaking to something like this: const query = createQuery({
// ...
retry: {
source: locationQuery,
timeout: (params, { retryNumber }) => retryNumber * 10,
retries: 2,
}:
}) Yeah, it's easier to cover with types and we can provide helpers in In my mind, this approach leads API to blowing, but I want to keep it small and composable. EcosystemFarfetched is based on Effetor, let's look on its API 👇 const $attempt = createStore(0)
const newAttempt = createEvent()
sample({
clock: newAttemp,
source: $attempt,
target: attempt => attempt + 1,
target: $attempt,
}) It uses separate methods to create connection, why we do not worry about duplication? What if we write domething like this 👇 const $attempt = createStore(0)
const newAttempt = createEvent()
sample({
clock: newAttemp,
source: $attempt,
target: attempt => attempt + 1,
target: $attempt,
})
sample({
clock: newAttemp,
source: $attempt,
target: attempt => attempt + 15,
target: $attempt,
}) Of course, it would break application logic, but it is okay, because it is explicit error in the code. So, now let's rewrite future const $attempt = createStore(0)
const newAttempt = createEvent()
sample({
clock: newAttemp,
source: $attempt,
target: attempt => attempt + 1,
target: $attempt,
})
sample({
clock: query.finished.failure,
source: $attempt,
filter: (attempt, { params, error }) => isNetworkError(error) && attempt < 10,
fn: (_, { params }) => params,
target: [newAttempt, query.start]
}) As you can see, it is only couple of samples. Now, it feels weird to rewrite in the inline way, isn't it? const newAttempt = createEvent()
const $attempt = createStore(0, {
[newAttempt]: attempt => attempt + 1
})
// ... ConslusionI sure, that suggest API (method Sorry for such a long answer 😹 Are you convinced now? |
Oh, I forgot to write the part about composable API's, let me do it now 🤗 Separate methods for modified Query behaviour has one another benefit — composability. Let's imagine, you want to create some helper for your internal Queries (I mean, in your company). With separate methods you can simply create you own method and apply it to any Query 👇 function enhanceExploreQuery(query) {
retry({ source: query, /* ... */ })
cache({ source: query, /* ... */ })
} Created enhancer could be used for any Query — created by built-in factories, custom factories or whatever. I cannot imagine the same for inline configs way without breaking types or DX for custom factories creators 🤔 |
These are good arguments, thanks! Maybe this operator needs some additional "context-based" filter then? I can imagine a case, where retry rules are different depending on, e.g. which page user is currently on Something like that: retry({
enabled: profilePage.$open,
source: locationQuery,
filter: isNetworkError,
timeout: (params, { retryNumber }) => retryNumber * 10,
retries: 2,
}) |
Does it cover case with lost authentication? Like Or this is out of scope for this API? |
It looks like a case for something like |
I would not say this is an issue, more like a use case :) But I can describe it separately nonetheless, sure. As far as I understand, this API is for unexpected floating errors, like "429 Too Many Requests", whereas lost authentication is an expected error. Am I right? |
Yeah, you are right 🤗 |
After some discussion, we have decided to change |
This API will provide a declarative way to set up reties for particular Query or Mutation.
Use case
HTTP request could fail by many reasons:
Restrictions
Check on particular error
Requests with some errors could be retried, some could not.
The API have to provide a way to distinguish particular error before starting retry.
Dynamic timeout
If the error was caused by server problems, it could be dangerous to retry the request immediately — it could lead to "internal DDoS".
The API have to provide a way to define retry interval dynamically as a Sourced field.
Dynamic number of retries
Same as timeout.
Parameters modification
In some systems, it is important to tell the server that the current request is a retry.
The API have to provide a way to modify Query/Mutation parameters on every retry.
API proposal
Based on the provided use cases and restrictions, I purpose to add new operator
retry
with the following API:Usage example
The text was updated successfully, but these errors were encountered: