Skip to content

Commit f15ee51

Browse files
deodadtmm
authored andcommitted
support AbortSignal param in withRetry
1 parent 787c8c2 commit f15ee51

4 files changed

Lines changed: 93 additions & 1 deletion

File tree

src/actions/public/waitForTransactionReceipt.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -258,6 +258,7 @@ export async function waitForTransactionReceipt<
258258
{
259259
delay: retryDelay,
260260
retryCount,
261+
signal: requestOptions?.signal,
261262
},
262263
)
263264
retrying = false
@@ -314,6 +315,7 @@ export async function waitForTransactionReceipt<
314315
retryCount,
315316
shouldRetry: ({ error }) =>
316317
error instanceof BlockNotFoundError,
318+
signal: requestOptions?.signal,
317319
},
318320
)
319321
retrying = false

src/actions/wallet/waitForCallsStatus.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { BaseError } from '../../errors/base.js'
44
import { BundleFailedError } from '../../errors/calls.js'
55
import type { ErrorType } from '../../errors/utils.js'
66
import type { Chain } from '../../types/chain.js'
7+
import type { EIP1193RequestOptions } from '../../types/eip1193.js'
78
import { getAction } from '../../utils/getAction.js'
89
import { type ObserveErrorType, observe } from '../../utils/observe.js'
910
import { type PollErrorType, poll } from '../../utils/poll.js'
@@ -30,6 +31,10 @@ export type WaitForCallsStatusParameters = {
3031
* @default client.pollingInterval
3132
*/
3233
pollingInterval?: number | undefined
34+
/**
35+
* Request options.
36+
*/
37+
requestOptions?: EIP1193RequestOptions | undefined
3338
/**
3439
* Number of times to retry if the call bundle failed.
3540
* @default 4 (exponential backoff)
@@ -98,6 +103,7 @@ export async function waitForCallsStatus<chain extends Chain | undefined>(
98103
const {
99104
id,
100105
pollingInterval = client.pollingInterval,
106+
requestOptions,
101107
status = ({ statusCode }) => statusCode === 200 || statusCode >= 300,
102108
retryCount = 4,
103109
retryDelay = ({ count }) => ~~(1 << count) * 200, // exponential backoff
@@ -128,14 +134,15 @@ export async function waitForCallsStatus<chain extends Chain | undefined>(
128134
client,
129135
getCallsStatus,
130136
'getCallsStatus',
131-
)({ id })
137+
)({ id, requestOptions })
132138
if (throwOnFailure && result.status === 'failure')
133139
throw new BundleFailedError(result)
134140
return result
135141
},
136142
{
137143
retryCount,
138144
delay: retryDelay,
145+
signal: requestOptions?.signal,
139146
},
140147
)
141148
if (!status(result)) return

src/utils/promise/withRetry.test.ts

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,3 +154,69 @@ test('delay: fn', async () => {
154154
).rejects.toThrowError('test')
155155
expect(end > 1000 && end < 1020).toBe(true)
156156
})
157+
158+
test('signal: already aborted', async () => {
159+
let retryTimes = 0
160+
const controller = new AbortController()
161+
controller.abort()
162+
163+
await expect(
164+
withRetry(
165+
async () => {
166+
retryTimes++
167+
throw new Error('test')
168+
},
169+
{ signal: controller.signal },
170+
),
171+
).rejects.toThrow('This operation was aborted')
172+
expect(retryTimes).toBe(0)
173+
})
174+
175+
test('signal: aborted during retries', async () => {
176+
let retryTimes = 0
177+
const controller = new AbortController()
178+
179+
await expect(
180+
withRetry(
181+
async () => {
182+
retryTimes++
183+
if (retryTimes === 1) controller.abort()
184+
throw new Error('test')
185+
},
186+
{ signal: controller.signal, delay: 0 },
187+
),
188+
).rejects.toThrow('This operation was aborted')
189+
expect(retryTimes).toBe(1)
190+
})
191+
192+
test('signal: not aborted', async () => {
193+
let retryTimes = 0
194+
const controller = new AbortController()
195+
196+
const result = await withRetry(
197+
async () => {
198+
retryTimes++
199+
if (retryTimes < 2) throw new Error('test')
200+
return 'success'
201+
},
202+
{ signal: controller.signal, delay: 0 },
203+
)
204+
205+
expect(result).toBe('success')
206+
expect(retryTimes).toBe(2)
207+
})
208+
209+
test('signal: aborted with custom reason', async () => {
210+
const controller = new AbortController()
211+
const customError = new Error('Custom abort reason')
212+
controller.abort(customError)
213+
214+
await expect(
215+
withRetry(
216+
async () => {
217+
throw new Error('test')
218+
},
219+
{ signal: controller.signal },
220+
),
221+
).rejects.toThrow('Custom abort reason')
222+
})

src/utils/promise/withRetry.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ export type WithRetryParameters = {
1919
error: Error
2020
}) => Promise<boolean> | boolean)
2121
| undefined
22+
// AbortSignal to cancel retries.
23+
signal?: AbortSignal | undefined
2224
}
2325

2426
export type WithRetryErrorType = ErrorType
@@ -29,10 +31,25 @@ export function withRetry<data>(
2931
delay: delay_ = 100,
3032
retryCount = 2,
3133
shouldRetry = () => true,
34+
signal,
3235
}: WithRetryParameters = {},
3336
) {
3437
return new Promise<data>((resolve, reject) => {
38+
const rejectWithAbort = () => {
39+
reject(signal?.reason ?? new Error('Aborted'))
40+
}
41+
42+
if (signal?.aborted) {
43+
rejectWithAbort()
44+
return
45+
}
46+
3547
const attemptRetry = async ({ count = 0 } = {}) => {
48+
if (signal?.aborted) {
49+
rejectWithAbort()
50+
return
51+
}
52+
3653
const retry = async ({ error }: { error: Error }) => {
3754
const delay =
3855
typeof delay_ === 'function' ? delay_({ count, error }) : delay_

0 commit comments

Comments
 (0)