feat(utils): add lockId to getSynchronize

This commit is contained in:
Marek Polak
2024-09-19 16:02:55 +02:00
committed by martin
parent 621eb66acb
commit ba3580aeeb
2 changed files with 67 additions and 9 deletions

View File

@@ -1,6 +1,8 @@
/**
* Ensures that all async actions passed to the returned function are called
* immediately one after another, without interfering with each other.
* Optionally, it also takes `lockId` param, in which case only actions with
* the same lock id are blocking each other.
*
* Example:
*
@@ -8,23 +10,28 @@
* const synchronize = getSynchronize();
* synchronize(() => asyncAction1());
* synchronize(() => asyncAction2());
* synchronize(() => asyncAction3(), 'differentLockId');
* ```
*/
export const getSynchronize = () => {
let lock: Promise<unknown> | undefined;
const DEFAULT_ID = Symbol();
const locks: Record<keyof any, Promise<unknown>> = {};
return <T>(action: () => T): T extends Promise<unknown> ? T : Promise<T> => {
const newLock = (lock ?? Promise.resolve())
return <T>(
action: () => T,
lockId: keyof any = DEFAULT_ID,
): T extends Promise<unknown> ? T : Promise<T> => {
const newLock = (locks[lockId] ?? Promise.resolve())
.catch(() => {})
.then(action)
.finally(() => {
if (lock === newLock) {
lock = undefined;
if (locks[lockId] === newLock) {
delete locks[lockId];
}
});
lock = newLock;
locks[lockId] = newLock;
return lock as any;
return newLock as any;
};
};

View File

@@ -63,9 +63,60 @@ describe('getSynchronize', () => {
synchronize(() =>
sequence(['a', 3]).then(() => {
// 'c' registers after 'a' ended and while 'b' is running
delay(2).then(() => synchronize(() => sequence(['c', 3])));
delay(2).then(() => synchronize(() => sequence(['c', 3])).then(done));
}),
);
synchronize(() => sequence(['b', 8]).then(done));
synchronize(() => sequence(['b', 8]));
});
it('with keys', async () => {
let state1: any, state2: any;
await Promise.all([
synchronize(async () => {
state1 = 'a';
await delay(3);
expect(state1).toBe('a');
state1 = 'b';
await delay(9);
expect(state1).toBe('b');
state1 = 'c';
await delay(3);
expect(state1).toBe('c');
}, 'lock1'),
synchronize(async () => {
expect(state1).toBe('a');
state2 = 'g';
await delay(8);
expect(state2).toBe('g');
expect(state1).toBe('b');
state2 = 'h';
await delay(11);
expect(state2).toBe('h');
expect(state1).toBe('d');
state2 = 'i';
await delay(12);
expect(state2).toBe('i');
expect(state1).toBe('f');
}, 'lock2'),
synchronize(async () => {
state1 = 'd';
await delay(8);
expect(state1).toBe('d');
state1 = 'e';
await delay(4);
expect(state1).toBe('e');
state1 = 'f';
await delay(2);
expect(state1).toBe('f');
}, 'lock1'),
]);
});
});