Skip to content

Commit

Permalink
feat(javascript): add waitForTask in search client (#510)
Browse files Browse the repository at this point in the history
* 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]>
  • Loading branch information
eunjae-lee and shortcuts authored May 19, 2022
1 parent 0835bc8 commit 4f8d355
Show file tree
Hide file tree
Showing 7 changed files with 187 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
export * from './src/createAuth';
export * from './src/createEchoRequester';
export * from './src/createRetryablePromise';
export * from './src/cache';
export * from './src/transporter';
export * from './src/createAlgoliaAgent';
Expand Down
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,
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
Expand Up @@ -61,6 +61,7 @@ private void setDefaultGeneratorOptions() {
additionalProperties.put("capitalizedApiName", Utils.capitalize(apiName));
additionalProperties.put("algoliaAgent", Utils.capitalize(CLIENT));
additionalProperties.put("gitRepoId", "algoliasearch-client-javascript");
additionalProperties.put("isSearchClient", CLIENT.equals("search"));
}

/** Provides an opportunity to inspect and modify operation data before the code is generated. */
Expand Down
32 changes: 32 additions & 0 deletions templates/javascript/api-single.mustache
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@ import {
createTransporter,
getAlgoliaAgent,
shuffle,
{{#isSearchClient}}
createRetryablePromise,
{{/isSearchClient}}
} from '@experimental-api-clients-automation/client-common';
import type {
CreateClientOptions,
Expand All @@ -11,6 +14,9 @@ import type {
Request,
RequestOptions,
QueryParameters,
{{#isSearchClient}}
CreateRetryablePromiseOptions,
{{/isSearchClient}}
} from '@experimental-api-clients-automation/client-common';

{{#imports}}
Expand Down Expand Up @@ -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({
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}}
Expand Down

0 comments on commit 4f8d355

Please sign in to comment.