-
Notifications
You must be signed in to change notification settings - Fork 17
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
feat(javascript): add waitForTask in search client #510
Changes from 13 commits
0df5195
4d2afe2
4ac6938
480a7ad
ddc4c0e
775679b
bf39f85
73bc136
a867bdd
4e244fd
b433f7f
98ce6be
395dfe6
fbdb035
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,86 @@ | ||
import { createRetryablePromise } from '../createRetryablePromise'; | ||
|
||
describe('createRetryablePromise', () => { | ||
it('resolves promise after some retries', async () => { | ||
let calls = 0; | ||
const promise = createRetryablePromise({ | ||
func: () => { | ||
return new Promise((resolve) => { | ||
calls += 1; | ||
resolve(`success #${calls}`); | ||
}); | ||
}, | ||
validate: () => calls >= 3, | ||
}); | ||
|
||
await expect(promise).resolves.toEqual('success #3'); | ||
expect(calls).toBe(3); | ||
}); | ||
|
||
it('gets the rejection of the given promise via reject', async () => { | ||
let calls = 0; | ||
|
||
const promise = createRetryablePromise({ | ||
func: () => { | ||
return new Promise((resolve, reject) => { | ||
calls += 1; | ||
if (calls <= 3) { | ||
resolve('okay'); | ||
} else { | ||
reject(new Error('nope')); | ||
} | ||
}); | ||
}, | ||
validate: () => false, | ||
}); | ||
|
||
await expect(promise).rejects.toEqual( | ||
expect.objectContaining({ message: 'nope' }) | ||
); | ||
}); | ||
|
||
it('gets the rejection of the given promise via throw', async () => { | ||
let calls = 0; | ||
|
||
const promise = createRetryablePromise({ | ||
func: () => { | ||
return new Promise((resolve) => { | ||
calls += 1; | ||
if (calls <= 3) { | ||
resolve('okay'); | ||
} else { | ||
throw new Error('nope'); | ||
} | ||
}); | ||
}, | ||
validate: () => false, | ||
}); | ||
|
||
await expect(promise).rejects.toEqual( | ||
expect.objectContaining({ message: 'nope' }) | ||
); | ||
}); | ||
|
||
it('gets the rejection when it exceeds the max trial number', async () => { | ||
const MAX_TRIAL = 3; | ||
let calls = 0; | ||
|
||
const promise = createRetryablePromise({ | ||
func: () => { | ||
return new Promise((resolve) => { | ||
calls += 1; | ||
resolve('okay'); | ||
}); | ||
}, | ||
validate: () => false, | ||
maxTrial: MAX_TRIAL, | ||
}); | ||
|
||
await expect(promise).rejects.toEqual( | ||
expect.objectContaining({ | ||
message: 'The maximum number of trials exceeded. (3/3)', | ||
}) | ||
); | ||
expect(calls).toBe(MAX_TRIAL); | ||
}); | ||
}); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
import type { CreateRetryablePromiseOptions } from './types/CreateRetryablePromise'; | ||
|
||
/** | ||
* Return a promise that retry a task until it meets the condition. | ||
* | ||
* @param createRetryablePromiseOptions - The createRetryablePromise options. | ||
* @param createRetryablePromiseOptions.func - The function to run, which returns a promise. | ||
* @param createRetryablePromiseOptions.validate - The validator function. It receives the resolved return of `func`. | ||
* @param createRetryablePromiseOptions.maxTrial - The maximum number of trials. 10 by default. | ||
* @param createRetryablePromiseOptions.timeout - The function to decide how long to wait between tries. | ||
*/ | ||
export function createRetryablePromise<TResponse>({ | ||
func, | ||
eunjae-lee marked this conversation as resolved.
Show resolved
Hide resolved
|
||
validate, | ||
maxTrial = 10, | ||
timeout = (retryCount: number): number => Math.min(retryCount * 10, 1000), | ||
}: CreateRetryablePromiseOptions<TResponse>): Promise<TResponse> { | ||
let retryCount = 0; | ||
const retry = (): Promise<TResponse> => { | ||
return new Promise<TResponse>((resolve, reject) => { | ||
func() | ||
.then((response) => { | ||
const isValid = validate(response); | ||
if (isValid) { | ||
resolve(response); | ||
} else if (retryCount + 1 >= maxTrial) { | ||
reject( | ||
new Error( | ||
`The maximum number of trials exceeded. (${ | ||
retryCount + 1 | ||
}/${maxTrial})` | ||
) | ||
); | ||
} else { | ||
retryCount += 1; | ||
setTimeout(() => { | ||
retry().then(resolve).catch(reject); | ||
}, timeout(retryCount)); | ||
} | ||
}) | ||
.catch((error) => { | ||
reject(error); | ||
}); | ||
}); | ||
}; | ||
|
||
return retry(); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
export type CreateRetryablePromiseOptions<TResponse> = { | ||
/** | ||
* The function to run, which returns a promise. | ||
*/ | ||
func: () => Promise<TResponse>; | ||
/** | ||
* The validator function. It receives the resolved return of `func`. | ||
*/ | ||
validate: (response: TResponse) => boolean; | ||
/** | ||
* The maximum number of trials. 10 by default. | ||
*/ | ||
maxTrial?: number; | ||
/** | ||
* The function to decide how long to wait between tries. | ||
*/ | ||
timeout?: (retryCount: number) => number; | ||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,6 @@ | ||
export * from './Cache'; | ||
export * from './CreateClient'; | ||
export * from './CreateRetryablePromise'; | ||
export * from './Host'; | ||
export * from './Requester'; | ||
export * from './Transporter'; |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -3,6 +3,9 @@ import { | |
createTransporter, | ||
getAlgoliaAgent, | ||
shuffle, | ||
{{#isSearchClient}} | ||
createRetryablePromise, | ||
{{/isSearchClient}} | ||
} from '@experimental-api-clients-automation/client-common'; | ||
import type { | ||
CreateClientOptions, | ||
|
@@ -11,6 +14,9 @@ import type { | |
Request, | ||
RequestOptions, | ||
QueryParameters, | ||
{{#isSearchClient}} | ||
CreateRetryablePromiseOptions, | ||
{{/isSearchClient}} | ||
} from '@experimental-api-clients-automation/client-common'; | ||
|
||
{{#imports}} | ||
|
@@ -104,6 +110,32 @@ export function create{{capitalizedApiName}}(options: CreateClientOptions{{#hasR | |
|
||
return { | ||
addAlgoliaAgent, | ||
{{#isSearchClient}} | ||
/** | ||
* Wait for a task to complete with `indexName` and `taskID`. | ||
* | ||
* @summary Wait for a task to complete. | ||
* @param waitForTaskProps - The waitForTaskProps object. | ||
* @param waitForTaskProps.indexName - The index in which to perform the request. | ||
* @param waitForTaskProps.taskID - The unique identifier of the task to wait for. | ||
*/ | ||
waitForTask({ | ||
shortcuts marked this conversation as resolved.
Show resolved
Hide resolved
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should we add a task to the backlog to write tests for those methods? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm not sure if we need a test for |
||
indexName, | ||
taskID, | ||
...createRetryablePromiseOptions, | ||
}: { | ||
indexName: string; | ||
taskID: number; | ||
} & Omit<CreateRetryablePromiseOptions<GetTaskResponse>, 'func' | 'validate'>): Promise<void> { | ||
return new Promise<void>((resolve, reject) => { | ||
createRetryablePromise<GetTaskResponse>({ | ||
...createRetryablePromiseOptions, | ||
func: () => this.getTask({ indexName, taskID }), | ||
validate: (response) => response.status === 'published', | ||
}).then(() => resolve()).catch(reject); | ||
}); | ||
}, | ||
{{/isSearchClient}} | ||
{{#operation}} | ||
/** | ||
{{#notes}} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we provide some docs here pls?