Skip to content

Commit

Permalink
improve retry behavior, add retry on set
Browse files Browse the repository at this point in the history
  • Loading branch information
jmeistrich committed Oct 29, 2023
1 parent 08efd93 commit 7799101
Show file tree
Hide file tree
Showing 7 changed files with 79 additions and 17 deletions.
2 changes: 2 additions & 0 deletions index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ export { ObservablePrimitiveClass } from './src/ObservablePrimitive';
import { get, getProxy, observableFns, peek, set } from './src/ObservableObject';
import { ensureNodeValue, findIDKey, getNode, globalState, optimized, setNodeValue, symbolDelete } from './src/globals';
import { setAtPath } from './src/helpers';
import { setupRetry } from './src/retry';

export const internal = {
ensureNodeValue,
Expand All @@ -71,5 +72,6 @@ export const internal = {
set,
setAtPath,
setNodeValue,
setupRetry,
symbolDelete,
};
4 changes: 3 additions & 1 deletion persist.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,9 @@ declare module '@legendapp/state' {
updateLastSync: (lastSync: number) => void;
retry: (options?: RetryOptions) => void;
}
// interface OnSetExtra {}
interface OnSetExtra {
onError: () => void;
}
// interface SubscribeOptions {}
// eslint-disable-next-line @typescript-eslint/no-empty-interface
interface ObservableState extends ObservablePersistStateBase {}
Expand Down
3 changes: 2 additions & 1 deletion src/ObservableObject.ts
Original file line number Diff line number Diff line change
Expand Up @@ -927,6 +927,7 @@ function activateNodeFunction(node: NodeValue, lazyFn: () => void) {
let update: UpdateFn;
let wasPromise: Promise<any> | undefined;
let timeoutRetry: { current?: any };
const attemptNum = { current: 0 };
const activator = (isFunction(node) ? node : lazyFn) as (value: ActivateProxyParams) => any;
const refresh = () => (node.state as ObservableObject<ObservablePersistStateInternal>).refreshNum.set((v) => v + 1);
observe(
Expand Down Expand Up @@ -981,7 +982,7 @@ function activateNodeFunction(node: NodeValue, lazyFn: () => void) {
if (timeoutRetry) {
clearTimeout(timeoutRetry.current);
}
const { handleError, timeout } = setupRetry(retryOptions, refresh);
const { handleError, timeout } = setupRetry(retryOptions, refresh, attemptNum);
onError = handleError;
timeoutRetry = timeout;
}
Expand Down
6 changes: 5 additions & 1 deletion src/observableInterfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -550,7 +550,11 @@ export interface ActivateParams<T = any> {
export interface ActivateProxyParams<T = any> extends ActivateParams {
proxy: (fn: (key: string, params: ActivateParams<T>) => T | Promise<T>) => void;
}
export type UpdateFn = (params: ObservableOnChangeParams) => void;
export type UpdateFn = (params: {
value: unknown;
mode?: 'assign' | 'set' | 'dateModified';
dateModified?: number | undefined;
}) => void;
export interface RetryOptions {
infinite?: boolean;
times?: number;
Expand Down
37 changes: 27 additions & 10 deletions src/persist/persistActivateNode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import type {
} from '@legendapp/state';
import { internal, isPromise, mergeIntoObservable } from '@legendapp/state';
import { persistObservable } from './persistObservable';
const { getProxy, globalState } = internal;
const { getProxy, globalState, setupRetry } = internal;

export function persistActivateNode() {
globalState.activateNode = function activateNodePersist(
Expand Down Expand Up @@ -40,16 +40,33 @@ export function persistActivateNode() {
// TODO: Work out these types better
pluginRemote.set = async (params: ObservablePersistRemoteSetParams<any>) => {
if (node.state?.isLoaded.get()) {
let changes = {};
let maxModified = 0;
await onSetFn(params as unknown as ListenerParams, {
update: (params) => {
const { value, dateModified } = params;
maxModified = Math.max(dateModified || 0, maxModified);
changes = mergeIntoObservable(changes, value);
},
return new Promise((resolve) => {
const attemptNum = { current: 0 };
const run = async () => {
let changes = {};
let maxModified = 0;
let didError = false;
let onError: () => void;
if (retryOptions) {
onError = setupRetry(retryOptions, run, attemptNum).handleError;
}
await onSetFn(params as unknown as ListenerParams, {
update: (params) => {
const { value, dateModified } = params;
maxModified = Math.max(dateModified || 0, maxModified);
changes = mergeIntoObservable(changes, value);
},
onError: () => {
didError = true;
onError?.();
},
});
if (!didError) {
resolve({ changes, dateModified: maxModified || undefined });
}
};
run();
});
return { changes, dateModified: maxModified || undefined };
}
};
}
Expand Down
10 changes: 6 additions & 4 deletions src/retry.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import type { RetryOptions } from './observableInterfaces';

export function setupRetry(retryOptions: RetryOptions, refresh: () => void) {
let attemptNum = 0;
export function setupRetry(retryOptions: RetryOptions, refresh: () => void, attemptNum: { current: number }) {
const timeout: { current?: any } = {};
let didGiveUp = false;
const { backoff, delay = 1000, infinite, times = 3, maxDelay = 30000 } = retryOptions;
let handleError: () => void;
if (infinite || attemptNum++ < times) {
const delayTime = Math.min(delay * (backoff === 'constant' ? 1 : 2 ** attemptNum), maxDelay);
attemptNum.current++;
if (infinite || attemptNum.current < times) {
const delayTime = Math.min(delay * (backoff === 'constant' ? 1 : 2 ** attemptNum.current), maxDelay);
handleError = () => {
timeout.current = setTimeout(refresh, delayTime);
};
Expand All @@ -23,6 +23,8 @@ export function setupRetry(retryOptions: RetryOptions, refresh: () => void) {
clearTimeout(timeout.current);
timeout.current = undefined;
}
// Restart the backoff when coming back online
attemptNum.current = 0;
didGiveUp = false;
refresh();
}
Expand Down
34 changes: 34 additions & 0 deletions tests/computed-persist.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -169,4 +169,38 @@ describe('retry', () => {
await promiseTimeout(0);
expect(obs$.get()).toEqual('hi');
});
test('retry a set', async () => {
const attemptNum$ = observable(0);
let saved = false;
const obs$ = observable(async ({ retry, onSet }: ActivateParams) => {
retry({
delay: 1,
});

onSet(({ value }, { onError }) => {

Check warning on line 180 in tests/computed-persist.test.ts

View workflow job for this annotation

GitHub Actions / Lint

'value' is defined but never used
return new Promise((resolve, reject) => {

Check warning on line 181 in tests/computed-persist.test.ts

View workflow job for this annotation

GitHub Actions / Lint

'resolve' is defined but never used

Check warning on line 181 in tests/computed-persist.test.ts

View workflow job for this annotation

GitHub Actions / Lint

'reject' is defined but never used
attemptNum$.set((v) => v + 1);
if (attemptNum$.get() > 2) {
saved = true;
} else {
onError();
}
});
});

return 1;
});

obs$.get();

expect(attemptNum$.get()).toEqual(0);
obs$.set(1);
await when(() => attemptNum$.get() === 1);
expect(attemptNum$.get()).toEqual(1);
expect(saved).toEqual(false);
await when(() => attemptNum$.get() === 2);
expect(saved).toEqual(false);
await when(() => attemptNum$.get() === 3);
expect(saved).toEqual(true);
});
});

0 comments on commit 7799101

Please sign in to comment.