feat(stdlib): new modules + eslint/tsconfig migration
- Add array/async/etc. modules and type tests; migrate to eslint flat config and composite tsconfig (vitest typecheck enabled). - Fix PubSub.emit to snapshot listeners before iterating (stable EventEmitter semantics; avoids invoking listeners added during the same emit).
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import { describe, it, expect, beforeEach, vi } from 'vitest';
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
import { SyncMutex } from '.';
|
||||
|
||||
describe('SyncMutex', () => {
|
||||
@@ -91,4 +91,21 @@ describe('SyncMutex', () => {
|
||||
|
||||
expect(mutex.isLocked).toBe(false);
|
||||
});
|
||||
|
||||
it('releases the lock and rethrows when the callback throws synchronously', async () => {
|
||||
await expect(mutex.execute(() => {
|
||||
throw new Error('boom');
|
||||
})).rejects.toThrow('boom');
|
||||
|
||||
expect(mutex.isLocked).toBe(false);
|
||||
|
||||
// The mutex must still be usable afterwards.
|
||||
await expect(mutex.execute(() => Promise.resolve('ok'))).resolves.toBe('ok');
|
||||
});
|
||||
|
||||
it('releases the lock and rejects when the callback returns a rejecting promise', async () => {
|
||||
await expect(mutex.execute(() => Promise.reject(new Error('async boom')))).rejects.toThrow('async boom');
|
||||
|
||||
expect(mutex.isLocked).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -37,9 +37,14 @@ export class SyncMutex {
|
||||
return;
|
||||
|
||||
this.lock();
|
||||
const result = await callback();
|
||||
this.unlock();
|
||||
|
||||
return result;
|
||||
// try/finally guarantees the lock is released even if the callback throws or
|
||||
// rejects — otherwise a single failure would wedge the guarded section forever.
|
||||
try {
|
||||
return await callback();
|
||||
}
|
||||
finally {
|
||||
this.unlock();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user