Skip to content

Commit 4f8d355

Browse files
feat(javascript): add waitForTask in search client (#510)
* WIP * feat(javascript): add waitForTask in search client * chore: export createRetryablePromise * chore: fix return type * fix: change taskID to number * Update templates/javascript/api-single.mustache Co-authored-by: Clément Vannicatte <[email protected]> * provide type and add comment * fix errors * add comments to the type * return nothing from waitForTask Co-authored-by: Clément Vannicatte <[email protected]>
1 parent 0835bc8 commit 4f8d355

File tree

7 files changed

+187
-0
lines changed

7 files changed

+187
-0
lines changed

clients/algoliasearch-client-javascript/packages/client-common/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
export * from './src/createAuth';
22
export * from './src/createEchoRequester';
3+
export * from './src/createRetryablePromise';
34
export * from './src/cache';
45
export * from './src/transporter';
56
export * from './src/createAlgoliaAgent';
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
import { createRetryablePromise } from '../createRetryablePromise';
2+
3+
describe('createRetryablePromise', () => {
4+
it('resolves promise after some retries', async () => {
5+
let calls = 0;
6+
const promise = createRetryablePromise({
7+
func: () => {
8+
return new Promise((resolve) => {
9+
calls += 1;
10+
resolve(`success #${calls}`);
11+
});
12+
},
13+
validate: () => calls >= 3,
14+
});
15+
16+
await expect(promise).resolves.toEqual('success #3');
17+
expect(calls).toBe(3);
18+
});
19+
20+
it('gets the rejection of the given promise via reject', async () => {
21+
let calls = 0;
22+
23+
const promise = createRetryablePromise({
24+
func: () => {
25+
return new Promise((resolve, reject) => {
26+
calls += 1;
27+
if (calls <= 3) {
28+
resolve('okay');
29+
} else {
30+
reject(new Error('nope'));
31+
}
32+
});
33+
},
34+
validate: () => false,
35+
});
36+
37+
await expect(promise).rejects.toEqual(
38+
expect.objectContaining({ message: 'nope' })
39+
);
40+
});
41+
42+
it('gets the rejection of the given promise via throw', async () => {
43+
let calls = 0;
44+
45+
const promise = createRetryablePromise({
46+
func: () => {
47+
return new Promise((resolve) => {
48+
calls += 1;
49+
if (calls <= 3) {
50+
resolve('okay');
51+
} else {
52+
throw new Error('nope');
53+
}
54+
});
55+
},
56+
validate: () => false,
57+
});
58+
59+
await expect(promise).rejects.toEqual(
60+
expect.objectContaining({ message: 'nope' })
61+
);
62+
});
63+
64+
it('gets the rejection when it exceeds the max trial number', async () => {
65+
const MAX_TRIAL = 3;
66+
let calls = 0;
67+
68+
const promise = createRetryablePromise({
69+
func: () => {
70+
return new Promise((resolve) => {
71+
calls += 1;
72+
resolve('okay');
73+
});
74+
},
75+
validate: () => false,
76+
maxTrial: MAX_TRIAL,
77+
});
78+
79+
await expect(promise).rejects.toEqual(
80+
expect.objectContaining({
81+
message: 'The maximum number of trials exceeded. (3/3)',
82+
})
83+
);
84+
expect(calls).toBe(MAX_TRIAL);
85+
});
86+
});
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import type { CreateRetryablePromiseOptions } from './types/CreateRetryablePromise';
2+
3+
/**
4+
* Return a promise that retry a task until it meets the condition.
5+
*
6+
* @param createRetryablePromiseOptions - The createRetryablePromise options.
7+
* @param createRetryablePromiseOptions.func - The function to run, which returns a promise.
8+
* @param createRetryablePromiseOptions.validate - The validator function. It receives the resolved return of `func`.
9+
* @param createRetryablePromiseOptions.maxTrial - The maximum number of trials. 10 by default.
10+
* @param createRetryablePromiseOptions.timeout - The function to decide how long to wait between tries.
11+
*/
12+
export function createRetryablePromise<TResponse>({
13+
func,
14+
validate,
15+
maxTrial = 10,
16+
timeout = (retryCount: number): number => Math.min(retryCount * 10, 1000),
17+
}: CreateRetryablePromiseOptions<TResponse>): Promise<TResponse> {
18+
let retryCount = 0;
19+
const retry = (): Promise<TResponse> => {
20+
return new Promise<TResponse>((resolve, reject) => {
21+
func()
22+
.then((response) => {
23+
const isValid = validate(response);
24+
if (isValid) {
25+
resolve(response);
26+
} else if (retryCount + 1 >= maxTrial) {
27+
reject(
28+
new Error(
29+
`The maximum number of trials exceeded. (${
30+
retryCount + 1
31+
}/${maxTrial})`
32+
)
33+
);
34+
} else {
35+
retryCount += 1;
36+
setTimeout(() => {
37+
retry().then(resolve).catch(reject);
38+
}, timeout(retryCount));
39+
}
40+
})
41+
.catch((error) => {
42+
reject(error);
43+
});
44+
});
45+
};
46+
47+
return retry();
48+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
export type CreateRetryablePromiseOptions<TResponse> = {
2+
/**
3+
* The function to run, which returns a promise.
4+
*/
5+
func: () => Promise<TResponse>;
6+
/**
7+
* The validator function. It receives the resolved return of `func`.
8+
*/
9+
validate: (response: TResponse) => boolean;
10+
/**
11+
* The maximum number of trials. 10 by default.
12+
*/
13+
maxTrial?: number;
14+
/**
15+
* The function to decide how long to wait between tries.
16+
*/
17+
timeout?: (retryCount: number) => number;
18+
};
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
export * from './Cache';
22
export * from './CreateClient';
3+
export * from './CreateRetryablePromise';
34
export * from './Host';
45
export * from './Requester';
56
export * from './Transporter';

generators/src/main/java/com/algolia/codegen/AlgoliaJavaScriptGenerator.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ private void setDefaultGeneratorOptions() {
6161
additionalProperties.put("capitalizedApiName", Utils.capitalize(apiName));
6262
additionalProperties.put("algoliaAgent", Utils.capitalize(CLIENT));
6363
additionalProperties.put("gitRepoId", "algoliasearch-client-javascript");
64+
additionalProperties.put("isSearchClient", CLIENT.equals("search"));
6465
}
6566

6667
/** Provides an opportunity to inspect and modify operation data before the code is generated. */

templates/javascript/api-single.mustache

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@ import {
33
createTransporter,
44
getAlgoliaAgent,
55
shuffle,
6+
{{#isSearchClient}}
7+
createRetryablePromise,
8+
{{/isSearchClient}}
69
} from '@experimental-api-clients-automation/client-common';
710
import type {
811
CreateClientOptions,
@@ -11,6 +14,9 @@ import type {
1114
Request,
1215
RequestOptions,
1316
QueryParameters,
17+
{{#isSearchClient}}
18+
CreateRetryablePromiseOptions,
19+
{{/isSearchClient}}
1420
} from '@experimental-api-clients-automation/client-common';
1521

1622
{{#imports}}
@@ -104,6 +110,32 @@ export function create{{capitalizedApiName}}(options: CreateClientOptions{{#hasR
104110

105111
return {
106112
addAlgoliaAgent,
113+
{{#isSearchClient}}
114+
/**
115+
* Wait for a task to complete with `indexName` and `taskID`.
116+
*
117+
* @summary Wait for a task to complete.
118+
* @param waitForTaskProps - The waitForTaskProps object.
119+
* @param waitForTaskProps.indexName - The index in which to perform the request.
120+
* @param waitForTaskProps.taskID - The unique identifier of the task to wait for.
121+
*/
122+
waitForTask({
123+
indexName,
124+
taskID,
125+
...createRetryablePromiseOptions,
126+
}: {
127+
indexName: string;
128+
taskID: number;
129+
} & Omit<CreateRetryablePromiseOptions<GetTaskResponse>, 'func' | 'validate'>): Promise<void> {
130+
return new Promise<void>((resolve, reject) => {
131+
createRetryablePromise<GetTaskResponse>({
132+
...createRetryablePromiseOptions,
133+
func: () => this.getTask({ indexName, taskID }),
134+
validate: (response) => response.status === 'published',
135+
}).then(() => resolve()).catch(reject);
136+
});
137+
},
138+
{{/isSearchClient}}
107139
{{#operation}}
108140
/**
109141
{{#notes}}

0 commit comments

Comments
 (0)