mirror of
https://github.com/trezor/trezor-suite.git
synced 2026-03-03 05:55:03 +01:00
feat(utils): getSynchronize
This commit is contained in:
25
packages/utils/src/getSynchronize.ts
Normal file
25
packages/utils/src/getSynchronize.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
/**
|
||||
* Ensures that all async actions passed to the returned function are called
|
||||
* immediately one after another, without interfering with each other.
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* ```
|
||||
* const synchronize = getSynchronize();
|
||||
* synchronize(() => asyncAction1());
|
||||
* synchronize(() => asyncAction2());
|
||||
* ```
|
||||
*/
|
||||
export const getSynchronize = () => {
|
||||
let lock: Promise<any> | undefined;
|
||||
|
||||
return <T>(action: () => T | Promise<T>): Promise<T> => {
|
||||
lock = (lock ?? Promise.resolve())
|
||||
.catch(() => {})
|
||||
.then(action)
|
||||
.finally(() => {
|
||||
lock = undefined;
|
||||
});
|
||||
return lock;
|
||||
};
|
||||
};
|
||||
@@ -12,6 +12,7 @@ export * from './createTimeoutPromise';
|
||||
export * as enumUtils from './enumUtils';
|
||||
export * from './getNumberFromPixelString';
|
||||
export * from './getRandomNumberInRange';
|
||||
export * from './getSynchronize';
|
||||
export * from './getWeakRandomId';
|
||||
export * from './hasUppercaseLetter';
|
||||
export * from './isAscii';
|
||||
|
||||
55
packages/utils/tests/getSynchronize.test.ts
Normal file
55
packages/utils/tests/getSynchronize.test.ts
Normal file
@@ -0,0 +1,55 @@
|
||||
import { getSynchronize } from '../src/getSynchronize';
|
||||
|
||||
const delay = (ms: number) => new Promise(resolve => setTimeout(resolve, ms));
|
||||
|
||||
const fail = (reason: string) => {
|
||||
throw new Error(reason);
|
||||
};
|
||||
|
||||
describe('getSynchronize', () => {
|
||||
let state: any;
|
||||
let synchronize: ReturnType<typeof getSynchronize>;
|
||||
|
||||
const sequence = async (...seq: [any, number][]) => {
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
for (const [str, ms] of seq) {
|
||||
state = str;
|
||||
// eslint-disable-next-line no-await-in-loop
|
||||
await delay(ms);
|
||||
expect(state).toBe(str);
|
||||
}
|
||||
};
|
||||
|
||||
const sync = (value: any) => (state = value);
|
||||
|
||||
beforeEach(() => {
|
||||
state = 'init';
|
||||
synchronize = getSynchronize();
|
||||
});
|
||||
|
||||
it('basic', async () => {
|
||||
await Promise.all([
|
||||
synchronize(() => sequence(['init', 3], [42, 9], [null, 3])),
|
||||
synchronize(() => sequence(['init', 5], ['boo', 3], [{ foo: 'bar' }, 6])),
|
||||
synchronize(() => sequence([undefined, 8], [[1, 2, 3], 4], [NaN, 2])),
|
||||
]);
|
||||
});
|
||||
|
||||
it('sync X async', async () => {
|
||||
await Promise.all([
|
||||
expect(synchronize(() => sync('foo'))).resolves.toBe('foo'),
|
||||
synchronize(() => sequence([0, 3], ['a', 5])),
|
||||
expect(synchronize(() => sync([null, null]))).resolves.toStrictEqual([null, null]),
|
||||
]);
|
||||
});
|
||||
|
||||
it('with errors', async () => {
|
||||
await Promise.all([
|
||||
synchronize(() => sequence(['a', 9])),
|
||||
expect(synchronize(() => delay(5).then(() => fail('err')))).rejects.toThrow('err'),
|
||||
synchronize(() => sequence(['b', 11])),
|
||||
expect(synchronize(() => fail('err'))).rejects.toThrow('err'),
|
||||
synchronize(() => sequence(['c', 7])),
|
||||
]);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user