Skip to content

Commit 60a7453

Browse files
committed
🏭 Add ability to resolve with latest response to the retry middleware. #141
1 parent 7a5620c commit 60a7453

File tree

3 files changed

+80
-46
lines changed

3 files changed

+80
-46
lines changed

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -605,7 +605,8 @@ wretch().middlewares([
605605
maxAttempts: 10,
606606
until: (response, error) => response && response.ok,
607607
onRetry: null,
608-
retryOnNetworkError: false
608+
retryOnNetworkError: false,
609+
resolveWithLatestReponse: false
609610
})
610611
])./* ... */
611612

src/middlewares/retry.ts

Lines changed: 69 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -12,56 +12,82 @@ export type OnRetryFunction = (args: {
1212
options: WretchOptions
1313
}) => void | OnRetryFunctionResponse | Promise<OnRetryFunctionResponse>
1414
export type RetryOptions = {
15+
/**
16+
* The timer between each attempt in milliseconds.
17+
*
18+
* _Default: `500`_
19+
*/
1520
delayTimer?: number,
21+
/**
22+
* The custom function that is used to calculate the actual delay based on the the timer & the number of attemps.
23+
*
24+
* _Default: `delay * nbOfAttemps`_
25+
*/
1626
delayRamp?: DelayRampFunction,
27+
/**
28+
* The maximum number of retries before resolving the promise with the last error. Specifying 0 means infinite retries.
29+
*
30+
* _Default: `10`_
31+
*/
1732
maxAttempts?: number,
33+
/**
34+
* The request will be retried until that condition is satisfied.
35+
*
36+
* _Default: `response && response.ok`_
37+
*/
1838
until?: UntilFunction,
39+
/**
40+
* Callback that will get executed before retrying the request. If this function returns an object having url and/or options properties, they will override existing values in the retried request.
41+
*
42+
* _Default: `undefined`_
43+
*/
1944
onRetry?: OnRetryFunction,
45+
/**
46+
* If true, will retry the request if a network error was thrown. Will also provide an 'error' argument to the `onRetry` and `until` methods.
47+
*
48+
* _Default: `false`_
49+
*/
2050
retryOnNetworkError?: boolean,
51+
/**
52+
* If true, the request will be resolved with the latest response instead of rejected with an error.
53+
*
54+
* _Default: `false`_
55+
*/
56+
resolveWithLatestReponse?: boolean
2157
}
2258

2359
/**
2460
* ## Retry middleware
2561
*
2662
* #### Retries a request multiple times in case of an error (or until a custom condition is true).
2763
*
28-
* **Options**
29-
*
30-
* - *delayTimer* `milliseconds`
31-
*
32-
* > The timer between each attempt.
33-
*
34-
* > *(default: 500)*
35-
*
36-
* - *delayRamp* `(delay, nbOfAttempts) => milliseconds`
37-
*
38-
* > The custom function that is used to calculate the actual delay based on the the timer & the number of attemps.
39-
*
40-
* > *(default: delay * nbOfAttemps)*
41-
*
42-
* - *maxAttempts* `number`
43-
*
44-
* > The maximum number of retries before resolving the promise with the last error. Specifying 0 means infinite retries.
45-
*
46-
* > *(default: 10)*
47-
*
48-
* - *until* `(response, error) => boolean || Promise<boolean>`
49-
*
50-
* > The request will be retried until that condition is satisfied.
51-
*
52-
* > *(default: response && response.ok)*
53-
*
54-
* - *onRetry* `({ response, error, url, options }) => { url?, options? } || Promise<{url?, options?}>`
55-
*
56-
* > Callback that will get executed before retrying the request. If this function returns an object having url and/or options properties, they will override existing values in the retried request.
57-
*
58-
* > *(default: null)*
59-
*
60-
* - *retryOnNetworkError* `boolean`
61-
*
62-
* > If true, will retry the request if a network error was thrown. Will also provide an 'error' argument to the `onRetry` and `until` methods.
63-
*
64-
* > *(default: false)*
64+
* ```ts
65+
* import wretch from 'wretch'
66+
* import { retry } from 'wretch/middlewares'
67+
*
68+
* wretch().middlewares([
69+
* retry({
70+
* // Options - defaults below
71+
* delayTimer: 500,
72+
* delayRamp: (delay, nbOfAttempts) => delay * nbOfAttempts,
73+
* maxAttempts: 10,
74+
* until: (response, error) => response && response.ok,
75+
* onRetry: null,
76+
* retryOnNetworkError: false,
77+
* resolveWithLatestReponse: false
78+
* })
79+
* ])
80+
*
81+
* // You can also return a Promise, which is useful if you want to inspect the body:
82+
* wretch().middlewares([
83+
* retry({
84+
* until: response =>
85+
* response.clone().json().then(body =>
86+
* body.field === 'something'
87+
* )
88+
* })
89+
* ])
90+
* ```
6591
*/
6692
export type RetryMiddleware = (options?: RetryOptions) => ConfiguredMiddleware
6793

@@ -78,15 +104,16 @@ export const retry: RetryMiddleware = ({
78104
maxAttempts = 10,
79105
until = defaultUntil,
80106
onRetry = null,
81-
retryOnNetworkError = false
107+
retryOnNetworkError = false,
108+
resolveWithLatestReponse = false
82109
} = {}) => {
83110

84111
return next => (url, opts) => {
85112
let numberOfAttemptsMade = 0
86113

87114
const checkStatus = (response?: Response, error?: Error) => {
88115
return Promise.resolve(until(response, error)).then(done => {
89-
// If the response is unexpected
116+
// If the response is not suitable
90117
if (!done) {
91118
numberOfAttemptsMade++
92119

@@ -102,7 +129,7 @@ export const retry: RetryMiddleware = ({
102129
url,
103130
options: opts
104131
})).then((values = {}) => {
105-
resolve(next((values as any).url ?? url, (values as any).options ?? opts))
132+
resolve(next((values && values.url) ?? url, (values && values.options) ?? opts))
106133
})
107134
} else {
108135
resolve(next(url, opts))
@@ -114,11 +141,11 @@ export const retry: RetryMiddleware = ({
114141
return checkStatus(null, error)
115142
})
116143
} else {
117-
return Promise.reject(error || new Error("Number of attempts exceeded."))
144+
return resolveWithLatestReponse ? response : Promise.reject(error || new Error("Number of attempts exceeded."))
118145
}
119146
}
120147

121-
return error ? Promise.reject(error) : response
148+
return resolveWithLatestReponse ? response : error ? Promise.reject(error) : response
122149
})
123150
}
124151

test/node/middlewares/retry.spec.ts

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ export default describe("Retry Middleware", () => {
5757
})
5858

5959
it("should execute 'onRetry'", async () => {
60-
let counter = 0;
60+
let counter = 0
6161
const w = base().middlewares([retry({
6262
delayTimer: 1,
6363
onRetry({ url, options, error }) {
@@ -73,7 +73,7 @@ export default describe("Retry Middleware", () => {
7373
})
7474

7575
it("should allow 'onRetry' to modify url and options", async () => {
76-
let counter = 0;
76+
let counter = 0
7777
const w = base().middlewares([retry({
7878
delayTimer: 1,
7979
onRetry() {
@@ -96,7 +96,7 @@ export default describe("Retry Middleware", () => {
9696
fail("Should never be called")
9797
}
9898
})], true)
99-
let counter = 0;
99+
let counter = 0
100100
const wRetry = wretch().polyfills({ fetch: throwPolyfill }).middlewares([retry({
101101
delayTimer: 1,
102102
retryOnNetworkError: true,
@@ -110,4 +110,10 @@ export default describe("Retry Middleware", () => {
110110
await expect(wRetry.get("/retry").res()).rejects.toThrowError("Network Error")
111111
expect(counter).toBe(10)
112112
})
113+
114+
it("should pass the latest response instead of throwing an error if resolveWithLatestReponse is true", async () => {
115+
const w = base().middlewares([retry({ delayTimer: 1, resolveWithLatestReponse: true })], true)
116+
// WretchError
117+
await expect(w.get("/retry").res()).rejects.toMatchObject({ response: { ok: false, counter: 12 } })
118+
})
113119
})

0 commit comments

Comments
 (0)