diff --git a/packages/utils/src/getSynchronize.ts b/packages/utils/src/getSynchronize.ts index de728c5cc5..bf7cc3910a 100644 --- a/packages/utils/src/getSynchronize.ts +++ b/packages/utils/src/getSynchronize.ts @@ -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 | undefined; + const DEFAULT_ID = Symbol(); + const locks: Record> = {}; - return (action: () => T): T extends Promise ? T : Promise => { - const newLock = (lock ?? Promise.resolve()) + return ( + action: () => T, + lockId: keyof any = DEFAULT_ID, + ): T extends Promise ? T : Promise => { + 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; }; }; diff --git a/packages/utils/tests/getSynchronize.test.ts b/packages/utils/tests/getSynchronize.test.ts index 8a87a95fa3..7d471dbec7 100644 --- a/packages/utils/tests/getSynchronize.test.ts +++ b/packages/utils/tests/getSynchronize.test.ts @@ -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'), + ]); }); });