Skip to content

Commit

Permalink
Implement waitAndRetry (#67)
Browse files Browse the repository at this point in the history
  • Loading branch information
kibertoad committed Aug 10, 2023
1 parent 6a0c365 commit 16096b1
Show file tree
Hide file tree
Showing 4 changed files with 125 additions and 0 deletions.
19 changes: 19 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -201,3 +201,22 @@ It's up to the caller of the function to handle the received error or throw an e
Read [this article](https://antman-does-software.com/stop-catching-errors-in-typescript-use-the-either-type-to-make-your-code-predictable) for more information on how `Either` works and its benefits.

Additionally, `DefineEither` is also provided. It is a variation of the aforementioned `Either`, which may or may not have `error` set, but always has `result`.

### waitAndRetry

There is helper function available for writing event-driven assertions in automated tests, which rely on something eventually happening:

```ts
import {waitAndRetry} from "@lokalise/node-core";

const result = await waitAndRetry(
() => {
return someEventEmitter.emittedEvents.length > 0
},
20, // sleepTime between attempts
30, // maxRetryCount before timeout
)

expect(result).toBe(false) // resolves to what the last attempt has returned
expect(someEventEmitter.emittedEvents.length).toBe(1)
```
2 changes: 2 additions & 0 deletions index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,3 +64,5 @@ export {
} from './src/errors/globalErrorHandler'

export type { MayOmit } from './src/common/may-omit'

export { waitAndRetry } from './src/utils/waitUtils'
70 changes: 70 additions & 0 deletions src/utils/waitUtils.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import { describe, expect } from 'vitest'

import { waitAndRetry } from './waitUtils'

class Counter {
private readonly timeOfSucces: number
public executionCounter: number
constructor(msecsTillSuccess: number) {
this.timeOfSucces = Date.now() + msecsTillSuccess
this.executionCounter = 0
}

process() {
this.executionCounter++
return Date.now() >= this.timeOfSucces
}
}

describe('waitUtils', () => {
describe('waitAndRetry', () => {
it('executes once if there is an instant condition match', async () => {
const counter = new Counter(0)

const result = await waitAndRetry(() => {
return counter.process()
})

expect(result).toBe(true)
expect(counter.executionCounter).toBe(1)
})

it('executes until there is a condition match', async () => {
const counter = new Counter(1000)

const result = await waitAndRetry(
() => {
return counter.process()
},
50,
30,
)

expect(result).toBe(true)
expect(counter.executionCounter > 0).toBe(true)
})

it('times out of there is never a condition match', async () => {
const counter = new Counter(1000)

const result = await waitAndRetry(
() => {
return counter.process()
},
20,
30,
)

expect(result).toBe(false)
expect(counter.executionCounter > 0).toBe(true)
})

it('handles an error', async () => {
await expect(
waitAndRetry(() => {
throw new Error('it broke')
}),
).rejects.toThrowError('it broke')
})
})
})
34 changes: 34 additions & 0 deletions src/utils/waitUtils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
export const waitAndRetry = async <T>(
predicateFn: () => T,
sleepTime = 20,
maxRetryCount = 15,
): Promise<T> => {
return new Promise((resolve, reject) => {
let retryCount = 0
function performCheck() {
// amount of retries exceeded
if (maxRetryCount !== 0 && retryCount > maxRetryCount) {
resolve(predicateFn())
}

// Try executing predicateFn
Promise.resolve()
.then(() => {
return predicateFn()
})
.then((result) => {
if (result) {
resolve(result)
} else {
retryCount++
setTimeout(performCheck, sleepTime)
}
})
.catch((err) => {
reject(err)
})
}

performCheck()
})
}

0 comments on commit 16096b1

Please sign in to comment.