diff --git a/__tests__/client.test.ts b/__tests__/client.test.ts index ee2b7b7..a2570d2 100644 --- a/__tests__/client.test.ts +++ b/__tests__/client.test.ts @@ -1,4 +1,5 @@ import { AnalyticsConnector } from '@amplitude/analytics-connector'; +import { FetchError } from '@amplitude/experiment-core'; import AsyncStorage from '@react-native-async-storage/async-storage'; import { Exposure } from '../lib/typescript'; @@ -61,6 +62,7 @@ class TestHttpClient implements HttpClient { return { status: this.status, body: this.body } as SimpleResponse; } } + /** * Basic test that fetching variants for a user succeeds. */ @@ -957,3 +959,45 @@ describe('start', () => { expect(fetchSpy).toBeCalledTimes(0); }); }); + +describe('fetch retry with different response codes', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + test.each([ + [300, 'Fetch Exception 300', 1], + [400, 'Fetch Exception 400', 0], + [429, 'Fetch Exception 429', 1], + [500, 'Fetch Exception 500', 1], + [0, 'Other Exception', 1], + ])( + 'responseCode=%p, errorMessage=%p, retryCalled=%p', + async (responseCode, errorMessage, retryCalled) => { + const client = new ExperimentClient(API_KEY, { + retryFetchOnFailure: true, + }); + + jest + .spyOn(ExperimentClient.prototype as any, 'doFetch') + .mockImplementation(async () => { + return new Promise((_resolve, reject) => { + if (responseCode === 0) { + reject(new Error(errorMessage)); + } else { + reject(new FetchError(responseCode, errorMessage)); + } + }); + }); + const retryMock = jest.spyOn( + ExperimentClient.prototype as any, + 'startRetries', + ); + try { + await client.fetch({ user_id: 'test_user' }); + } catch (e) { + // catch error + } + expect(retryMock).toHaveBeenCalledTimes(retryCalled); + }, + ); +}); diff --git a/package.json b/package.json index 152c016..da3d8ea 100644 --- a/package.json +++ b/package.json @@ -48,7 +48,7 @@ }, "dependencies": { "@amplitude/analytics-connector": "^1.4.7", - "@amplitude/experiment-core": "^0.7.0", + "@amplitude/experiment-core": "^0.7.2", "@react-native-async-storage/async-storage": "^1.17.6", "unfetch": "^4.2.0" }, diff --git a/src/experimentClient.ts b/src/experimentClient.ts index 1b0681d..dcce137 100644 --- a/src/experimentClient.ts +++ b/src/experimentClient.ts @@ -8,6 +8,7 @@ import { EvaluationApi, EvaluationEngine, EvaluationFlag, + FetchError, FlagApi, Poller, SdkFlagApi, @@ -607,7 +608,7 @@ export class ExperimentClient implements Client { this.debug(`[Experiment] Fetch all: retry=${retry}`); - // Proactively cancel retries if active in order to avoid unecessary API + // Proactively cancel retries if active in order to avoid unnecessary API // requests. A new failure will restart the retries. if (retry) { this.stopRetries(); @@ -618,7 +619,7 @@ export class ExperimentClient implements Client { await this.storeVariants(variants, options); return variants; } catch (e) { - if (retry) { + if (retry && this.shouldRetryFetch(e)) { this.startRetries(user, options); } throw e; @@ -780,6 +781,13 @@ export class ExperimentClient implements Client { console.debug(message, ...optionalParams); } } + + private shouldRetryFetch(e: Error): boolean { + if (e instanceof FetchError) { + return e.statusCode < 400 || e.statusCode >= 500 || e.statusCode === 429; + } + return true; + } } type SourceVariant = { diff --git a/yarn.lock b/yarn.lock index e0197f6..df54e43 100644 --- a/yarn.lock +++ b/yarn.lock @@ -14,10 +14,10 @@ dependencies: "@amplitude/ua-parser-js" "^0.7.31" -"@amplitude/experiment-core@^0.7.0": - version "0.7.0" - resolved "https://registry.yarnpkg.com/@amplitude/experiment-core/-/experiment-core-0.7.0.tgz#258d95d691461acf3b6d1bea430f5ba231986342" - integrity sha512-/W+BFGc2qix4aZ9V4VEYDcot8Vk/YirRcqY9Y0hlB27NPnhke0Id5mgPl/puKy7wqbXC6yTuda7rVpAtGBFMOA== +"@amplitude/experiment-core@^0.7.2": + version "0.7.2" + resolved "https://registry.yarnpkg.com/@amplitude/experiment-core/-/experiment-core-0.7.2.tgz#f94219d68d86322e8d580c8fbe0672dcd29f86bb" + integrity sha512-Wc2NWvgQ+bLJLeF0A9wBSPIaw0XuqqgkPKsoNFQrmS7r5Djd56um75In05tqmVntPJZRvGKU46pAp8o5tdf4mA== dependencies: js-base64 "^3.7.5"