feat(utils): getSynchronize

This commit is contained in:
Marek Polak
2023-05-23 10:23:05 +02:00
committed by martin
parent 9ac099f565
commit 0b988ff596
3 changed files with 81 additions and 0 deletions

View 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;
};
};

View File

@@ -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';

View 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])),
]);
});
});