diff --git a/packages/core/src/client.ts b/packages/core/src/client.ts index 1de223b327c0..4cae0e0a5136 100644 --- a/packages/core/src/client.ts +++ b/packages/core/src/client.ts @@ -1079,7 +1079,7 @@ export abstract class Client { * @param event The event to send to Sentry. * @param hint May contain additional information about the original exception. * @param currentScope A scope containing event metadata. - * @returns A SyncPromise that resolves with the event or rejects in case event was/will not be send. + * @returns A PromiseLike that resolves with the event or rejects in case event was/will not be send. */ protected _processEvent( event: Event, diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index e0daefd54d76..f89720e3749e 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -242,7 +242,12 @@ export { supportsReferrerPolicy, supportsReportingObserver, } from './utils/supports'; -export { SyncPromise, rejectedSyncPromise, resolvedSyncPromise } from './utils/syncpromise'; +export { + // eslint-disable-next-line deprecation/deprecation + SyncPromise, + rejectedSyncPromise, + resolvedSyncPromise, +} from './utils/syncpromise'; export { browserPerformanceTimeOrigin, dateTimestampInSeconds, timestampInSeconds } from './utils/time'; export { TRACEPARENT_REGEXP, diff --git a/packages/core/src/utils/promisebuffer.ts b/packages/core/src/utils/promisebuffer.ts index 2830e8897129..1b4067abe831 100644 --- a/packages/core/src/utils/promisebuffer.ts +++ b/packages/core/src/utils/promisebuffer.ts @@ -1,9 +1,9 @@ -import { rejectedSyncPromise, resolvedSyncPromise, SyncPromise } from './syncpromise'; +import { rejectedSyncPromise, resolvedSyncPromise } from './syncpromise'; export interface PromiseBuffer { // exposes the internal array so tests can assert on the state of it. // XXX: this really should not be public api. - $: Array>; + $: PromiseLike[]; add(taskProducer: () => PromiseLike): PromiseLike; drain(timeout?: number): PromiseLike; } @@ -14,11 +14,11 @@ export const SENTRY_BUFFER_FULL_ERROR = Symbol.for('SentryBufferFullError'); * Creates an new PromiseBuffer object with the specified limit * @param limit max number of promises that can be stored in the buffer */ -export function makePromiseBuffer(limit?: number): PromiseBuffer { - const buffer: Array> = []; +export function makePromiseBuffer(limit: number = 100): PromiseBuffer { + const buffer: Set> = new Set(); function isReady(): boolean { - return limit === undefined || buffer.length < limit; + return buffer.size < limit; } /** @@ -27,8 +27,8 @@ export function makePromiseBuffer(limit?: number): PromiseBuffer { * @param task Can be any PromiseLike * @returns Removed promise. */ - function remove(task: PromiseLike): PromiseLike { - return buffer.splice(buffer.indexOf(task), 1)[0] || Promise.resolve(undefined); + function remove(task: PromiseLike): void { + buffer.delete(task); } /** @@ -48,19 +48,11 @@ export function makePromiseBuffer(limit?: number): PromiseBuffer { // start the task and add its promise to the queue const task = taskProducer(); - if (buffer.indexOf(task) === -1) { - buffer.push(task); - } - void task - .then(() => remove(task)) - // Use `then(null, rejectionHandler)` rather than `catch(rejectionHandler)` so that we can use `PromiseLike` - // rather than `Promise`. `PromiseLike` doesn't have a `.catch` method, making its polyfill smaller. (ES5 didn't - // have promises, so TS has to polyfill when down-compiling.) - .then(null, () => - remove(task).then(null, () => { - // We have to add another catch here because `remove()` starts a new promise chain. - }), - ); + buffer.add(task); + void task.then( + () => remove(task), + () => remove(task), + ); return task; } @@ -74,34 +66,27 @@ export function makePromiseBuffer(limit?: number): PromiseBuffer { * `false` otherwise */ function drain(timeout?: number): PromiseLike { - return new SyncPromise((resolve, reject) => { - let counter = buffer.length; + if (!buffer.size) { + return resolvedSyncPromise(true); + } - if (!counter) { - return resolve(true); - } + const drainPromise = Promise.all(Array.from(buffer)).then(() => true); + + if (!timeout) { + return drainPromise; + } - // wait for `timeout` ms and then resolve to `false` (if not cancelled first) - const capturedSetTimeout = setTimeout(() => { - if (timeout && timeout > 0) { - resolve(false); - } - }, timeout); + const promises = [drainPromise, new Promise(resolve => setTimeout(() => resolve(false), timeout))]; - // if all promises resolve in time, cancel the timer and resolve to `true` - buffer.forEach(item => { - void resolvedSyncPromise(item).then(() => { - if (!--counter) { - clearTimeout(capturedSetTimeout); - resolve(true); - } - }, reject); - }); - }); + // Promise.race will resolve to the first promise that resolves or rejects + // So if the drainPromise resolves, the timeout promise will be ignored + return Promise.race(promises); } return { - $: buffer, + get $(): PromiseLike[] { + return Array.from(buffer); + }, add, drain, }; diff --git a/packages/core/src/utils/syncpromise.ts b/packages/core/src/utils/syncpromise.ts index 0b885910fee3..af2db09222b0 100644 --- a/packages/core/src/utils/syncpromise.ts +++ b/packages/core/src/utils/syncpromise.ts @@ -7,6 +7,13 @@ const STATE_RESOLVED = 1; const STATE_REJECTED = 2; type State = typeof STATE_PENDING | typeof STATE_RESOLVED | typeof STATE_REJECTED; +type Executor = (resolve: (value?: T | PromiseLike | null) => void, reject: (reason?: any) => void) => void; +type PromiseTry = (executor: Executor) => PromiseLike; +type PromiseWithTry = PromiseConstructor & { try: PromiseTry }; + +function hasPromiseTry(Promise: typeof globalThis.Promise): Promise is PromiseWithTry { + return 'try' in Promise && typeof Promise.try === 'function'; +} // Overloads so we can call resolvedSyncPromise without arguments and generic argument export function resolvedSyncPromise(): PromiseLike; @@ -19,9 +26,8 @@ export function resolvedSyncPromise(value: T | PromiseLike): PromiseLike(value?: T | PromiseLike): PromiseLike { - return new SyncPromise(resolve => { - resolve(value); - }); + // eslint-disable-next-line deprecation/deprecation + return hasPromiseTry(Promise) ? Promise.try(() => value) : new SyncPromise(resolve => resolve(value)); } /** @@ -31,16 +37,19 @@ export function resolvedSyncPromise(value?: T | PromiseLike): PromiseLike< * @returns the rejected sync promise */ export function rejectedSyncPromise(reason?: any): PromiseLike { - return new SyncPromise((_, reject) => { - reject(reason); - }); + return hasPromiseTry(Promise) + ? Promise.try(() => { + throw reason; + }) + : // eslint-disable-next-line deprecation/deprecation + new SyncPromise((_, reject) => reject(reason)); } -type Executor = (resolve: (value?: T | PromiseLike | null) => void, reject: (reason?: any) => void) => void; - /** * Thenable class that behaves like a Promise and follows it's interface * but is not async internally + * + * @deprecated This export will be removed in a future version. */ export class SyncPromise implements PromiseLike { private _state: State; @@ -59,6 +68,7 @@ export class SyncPromise implements PromiseLike { onfulfilled?: ((value: T) => TResult1 | PromiseLike) | null, onrejected?: ((reason: any) => TResult2 | PromiseLike) | null, ): PromiseLike { + // eslint-disable-next-line deprecation/deprecation return new SyncPromise((resolve, reject) => { this._handlers.push([ false, @@ -100,6 +110,7 @@ export class SyncPromise implements PromiseLike { /** @inheritdoc */ public finally(onfinally?: (() => void) | null): PromiseLike { + // eslint-disable-next-line deprecation/deprecation return new SyncPromise((resolve, reject) => { let val: TResult | any; let isRejected: boolean; diff --git a/packages/core/test/lib/client.test.ts b/packages/core/test/lib/client.test.ts index c7cbe7ab4a97..a977db867978 100644 --- a/packages/core/test/lib/client.test.ts +++ b/packages/core/test/lib/client.test.ts @@ -8,7 +8,6 @@ import { makeSession, Scope, setCurrentClient, - SyncPromise, withMonitor, } from '../../src'; import * as integrationModule from '../../src/integration'; @@ -2111,7 +2110,7 @@ describe('Client', () => { const spy = vi.spyOn(TestClient.instance!, 'eventFromMessage'); spy.mockImplementationOnce( (message, level) => - new SyncPromise(resolve => { + new Promise(resolve => { setTimeout(() => resolve({ message, level }), 150); }), ); diff --git a/packages/core/test/lib/utils/promisebuffer.test.ts b/packages/core/test/lib/utils/promisebuffer.test.ts index 618de06322a0..dd78fe7689e1 100644 --- a/packages/core/test/lib/utils/promisebuffer.test.ts +++ b/packages/core/test/lib/utils/promisebuffer.test.ts @@ -1,52 +1,155 @@ import { describe, expect, test, vi } from 'vitest'; import { makePromiseBuffer } from '../../../src/utils/promisebuffer'; -import { SyncPromise } from '../../../src/utils/syncpromise'; +import { rejectedSyncPromise, resolvedSyncPromise } from '../../../src/utils/syncpromise'; describe('PromiseBuffer', () => { describe('add()', () => { - test('no limit', () => { - const buffer = makePromiseBuffer(); - const p = vi.fn(() => new SyncPromise(resolve => setTimeout(resolve))); - void buffer.add(p); - expect(buffer.$.length).toEqual(1); + test('enforces limit of promises', async () => { + const buffer = makePromiseBuffer(5); + + const producer1 = vi.fn(() => new Promise(resolve => setTimeout(resolve, 1))); + const producer2 = vi.fn(() => new Promise(resolve => setTimeout(resolve, 1))); + const producer3 = vi.fn(() => new Promise(resolve => setTimeout(resolve, 1))); + const producer4 = vi.fn(() => new Promise(resolve => setTimeout(resolve, 1))); + const producer5 = vi.fn(() => new Promise(resolve => setTimeout(resolve, 1))); + const producer6 = vi.fn(() => new Promise(resolve => setTimeout(resolve, 1))); + + void buffer.add(producer1); + void buffer.add(producer2); + void buffer.add(producer3); + void buffer.add(producer4); + void buffer.add(producer5); + void expect(buffer.add(producer6)).rejects.toThrowError(); + + expect(producer1).toHaveBeenCalledTimes(1); + expect(producer2).toHaveBeenCalledTimes(1); + expect(producer3).toHaveBeenCalledTimes(1); + expect(producer4).toHaveBeenCalledTimes(1); + expect(producer5).toHaveBeenCalledTimes(1); + expect(producer6).not.toHaveBeenCalled(); + + expect(buffer.$.length).toEqual(5); + + await buffer.drain(); + + expect(buffer.$.length).toEqual(0); + + expect(producer1).toHaveBeenCalledTimes(1); + expect(producer2).toHaveBeenCalledTimes(1); + expect(producer3).toHaveBeenCalledTimes(1); + expect(producer4).toHaveBeenCalledTimes(1); + expect(producer5).toHaveBeenCalledTimes(1); + expect(producer6).not.toHaveBeenCalled(); }); - test('with limit', () => { + test('sync promises', () => { const buffer = makePromiseBuffer(1); let task1; const producer1 = vi.fn(() => { - task1 = new SyncPromise(resolve => setTimeout(resolve)); + task1 = resolvedSyncPromise(); return task1; }); - const producer2 = vi.fn(() => new SyncPromise(resolve => setTimeout(resolve))); + const producer2 = vi.fn(() => resolvedSyncPromise()); + expect(buffer.add(producer1)).toEqual(task1); + void expect(buffer.add(producer2)).rejects.toThrowError(); + // This is immediately executed and removed again from the buffer + expect(buffer.$.length).toEqual(0); + expect(producer1).toHaveBeenCalled(); + expect(producer2).toHaveBeenCalled(); + }); + + test('async promises', () => { + const buffer = makePromiseBuffer(1); + let task1; + const producer1 = vi.fn(() => { + task1 = new Promise(resolve => setTimeout(resolve, 1)); + return task1; + }); + const producer2 = vi.fn(() => new Promise(resolve => setTimeout(resolve, 1))); expect(buffer.add(producer1)).toEqual(task1); void expect(buffer.add(producer2)).rejects.toThrowError(); expect(buffer.$.length).toEqual(1); expect(producer1).toHaveBeenCalled(); expect(producer2).not.toHaveBeenCalled(); }); + + test('handles multiple equivalent promises', async () => { + const buffer = makePromiseBuffer(10); + + const promise = new Promise(resolve => setTimeout(resolve, 1)); + + const producer = vi.fn(() => promise); + const producer2 = vi.fn(() => promise); + + expect(buffer.add(producer)).toEqual(promise); + expect(buffer.add(producer2)).toEqual(promise); + + expect(buffer.$.length).toEqual(1); + + expect(producer).toHaveBeenCalled(); + expect(producer2).toHaveBeenCalled(); + + await buffer.drain(); + + expect(buffer.$.length).toEqual(0); + }); }); describe('drain()', () => { - test('without timeout', async () => { + test('drains all promises without timeout', async () => { const buffer = makePromiseBuffer(); - for (let i = 0; i < 5; i++) { - void buffer.add(() => new SyncPromise(resolve => setTimeout(resolve))); - } + + const p1 = vi.fn(() => new Promise(resolve => setTimeout(resolve, 1))); + const p2 = vi.fn(() => new Promise(resolve => setTimeout(resolve, 1))); + const p3 = vi.fn(() => new Promise(resolve => setTimeout(resolve, 1))); + const p4 = vi.fn(() => new Promise(resolve => setTimeout(resolve, 1))); + const p5 = vi.fn(() => new Promise(resolve => setTimeout(resolve, 1))); + + [p1, p2, p3, p4, p5].forEach(p => { + void buffer.add(p); + }); + expect(buffer.$.length).toEqual(5); const result = await buffer.drain(); expect(result).toEqual(true); expect(buffer.$.length).toEqual(0); + + expect(p1).toHaveBeenCalled(); + expect(p2).toHaveBeenCalled(); + expect(p3).toHaveBeenCalled(); + expect(p4).toHaveBeenCalled(); + expect(p5).toHaveBeenCalled(); }); - test('with timeout', async () => { + test('drains all promises with timeout', async () => { const buffer = makePromiseBuffer(); - for (let i = 0; i < 5; i++) { - void buffer.add(() => new SyncPromise(resolve => setTimeout(resolve, 100))); - } + + const p1 = vi.fn(() => new Promise(resolve => setTimeout(resolve, 2))); + const p2 = vi.fn(() => new Promise(resolve => setTimeout(resolve, 4))); + const p3 = vi.fn(() => new Promise(resolve => setTimeout(resolve, 6))); + const p4 = vi.fn(() => new Promise(resolve => setTimeout(resolve, 8))); + const p5 = vi.fn(() => new Promise(resolve => setTimeout(resolve, 10))); + + [p1, p2, p3, p4, p5].forEach(p => { + void buffer.add(p); + }); + + expect(p1).toHaveBeenCalled(); + expect(p2).toHaveBeenCalled(); + expect(p3).toHaveBeenCalled(); + expect(p4).toHaveBeenCalled(); + expect(p5).toHaveBeenCalled(); + expect(buffer.$.length).toEqual(5); - const result = await buffer.drain(50); + const result = await buffer.drain(8); expect(result).toEqual(false); + // p5 is still in the buffer + expect(buffer.$.length).toEqual(1); + + // Now drain final item + const result2 = await buffer.drain(); + expect(result2).toEqual(true); + expect(buffer.$.length).toEqual(0); }); test('on empty buffer', async () => { @@ -60,7 +163,7 @@ describe('PromiseBuffer', () => { test('resolved promises should not show up in buffer length', async () => { const buffer = makePromiseBuffer(); - const producer = () => new SyncPromise(resolve => setTimeout(resolve)); + const producer = () => new Promise(resolve => setTimeout(resolve, 1)); const task = buffer.add(producer); expect(buffer.$.length).toEqual(1); await task; @@ -69,20 +172,18 @@ describe('PromiseBuffer', () => { test('rejected promises should not show up in buffer length', async () => { const buffer = makePromiseBuffer(); - const producer = () => new SyncPromise((_, reject) => setTimeout(reject)); + const error = new Error('whoops'); + const producer = () => new Promise((_, reject) => setTimeout(() => reject(error), 1)); const task = buffer.add(producer); expect(buffer.$.length).toEqual(1); - try { - await task; - } catch { - // no-empty - } + + await expect(task).rejects.toThrow(error); expect(buffer.$.length).toEqual(0); }); test('resolved task should give an access to the return value', async () => { const buffer = makePromiseBuffer(); - const producer = () => new SyncPromise(resolve => setTimeout(() => resolve('test'))); + const producer = () => resolvedSyncPromise('test'); const task = buffer.add(producer); const result = await task; expect(result).toEqual('test'); @@ -91,7 +192,7 @@ describe('PromiseBuffer', () => { test('rejected task should give an access to the return value', async () => { expect.assertions(1); const buffer = makePromiseBuffer(); - const producer = () => new SyncPromise((_, reject) => setTimeout(() => reject(new Error('whoops')))); + const producer = () => rejectedSyncPromise(new Error('whoops')); const task = buffer.add(producer); try { await task; diff --git a/packages/core/test/lib/utils/syncpromise.test.ts b/packages/core/test/lib/utils/syncpromise.test.ts index 14bc5d7abbe0..856aced86eb8 100644 --- a/packages/core/test/lib/utils/syncpromise.test.ts +++ b/packages/core/test/lib/utils/syncpromise.test.ts @@ -1,7 +1,21 @@ -import { describe, expect, test, vi } from 'vitest'; +/* eslint-disable deprecation/deprecation */ +import { afterAll, beforeAll, describe, expect, test, vi } from 'vitest'; import { rejectedSyncPromise, resolvedSyncPromise, SyncPromise } from '../../../src/utils/syncpromise'; describe('SyncPromise', () => { + // @ts-expect-error We want to ensure to test this without the native Promise.try + const promiseTry = Promise.try; + + beforeAll(() => { + // @ts-expect-error We want to ensure to test this without the native Promise.try + Promise.try = undefined; + }); + + afterAll(() => { + // @ts-expect-error Restore this + Promise.try = promiseTry; + }); + test('simple', async () => { expect.assertions(1); @@ -12,6 +26,35 @@ describe('SyncPromise', () => { }); }); + test('simple error catching', async () => { + expect.assertions(1); + + const error = new Error('test error'); + + return new SyncPromise(() => { + throw error; + }).catch(val => { + expect(val).toBe(error); + }); + }); + + test('simple error in then', async () => { + expect.assertions(1); + + const error = new Error('test error'); + + return new SyncPromise(() => { + throw error; + }).then( + () => { + throw new Error('THIS SHOULD NOT BE CALLED!'); + }, + val => { + expect(val).toBe(error); + }, + ); + }); + test('simple chaining', async () => { expect.assertions(1); @@ -98,6 +141,9 @@ describe('SyncPromise', () => { expect.assertions(1); const p = resolvedSyncPromise(10); + + console.log(p); + return p.then(val => { expect(val).toBe(10); }); @@ -266,3 +312,245 @@ describe('SyncPromise', () => { }); }); }); + +describe('resolvedSyncPromise', () => { + test('simple chaining', async () => { + expect.assertions(1); + + return new SyncPromise(resolve => { + resolve(42); + }) + .then(_ => resolvedSyncPromise('a')) + .then(_ => resolvedSyncPromise(0.1)) + .then(_ => resolvedSyncPromise(false)) + .then(val => { + expect(val).toBe(false); + }); + }); + + test('compare to regular promise', async () => { + expect.assertions(2); + + const ap = new Promise(resolve => { + resolve('1'); + }); + + const bp = new Promise(resolve => { + resolve('2'); + }); + + const cp = new Promise(resolve => { + resolve('3'); + }); + + const fp = async (s: PromiseLike, prepend: string) => + new Promise(resolve => { + void s + .then(val => { + resolve(prepend + val); + }) + .then(null, _ => { + // bla + }); + }); + + const res = await cp + .then(async val => fp(Promise.resolve('x'), val)) + .then(async val => fp(bp, val)) + .then(async val => fp(ap, val)); + + expect(res).toBe('3x21'); + + const a = new SyncPromise(resolve => { + resolve('1'); + }); + + const b = new SyncPromise(resolve => { + resolve('2'); + }); + + const c = new SyncPromise(resolve => { + resolve('3'); + }); + + const f = (s: SyncPromise, prepend: string) => + new SyncPromise(resolve => { + void s + .then(val => { + resolve(prepend + val); + }) + .then(null, () => { + // no-empty + }); + }); + + return ( + c + // @ts-expect-error Argument of type 'PromiseLike' is not assignable to parameter of type 'SyncPromise' + .then(val => f(resolvedSyncPromise('x'), val)) + .then(val => f(b, val)) + .then(val => f(a, val)) + .then(val => { + expect(val).toBe(res); + }) + ); + }); + + test('simple static', async () => { + expect.assertions(1); + + const p = resolvedSyncPromise(10); + + return p.then(val => { + expect(val).toBe(10); + }); + }); + + test('calling the callback immediately', () => { + expect.assertions(1); + + let foo: number = 1; + + new SyncPromise(_ => { + foo = 2; + }); + + expect(foo).toEqual(2); + }); + + test('calling the callback not immediately', () => { + vi.useFakeTimers(); + expect.assertions(4); + + const qp = new SyncPromise(resolve => + setTimeout(() => { + resolve(2); + }), + ); + void qp + .then(value => { + expect(value).toEqual(2); + }) + .then(null, () => { + // no-empty + }); + expect(qp).not.toHaveProperty('_value'); + void qp + .then(value => { + expect(value).toEqual(2); + }) + .then(null, () => { + // no-empty + }); + vi.runAllTimers(); + expect(qp).toHaveProperty('_value'); + }); + + test('multiple then returning undefined', async () => { + expect.assertions(3); + + return resolvedSyncPromise(2) + .then(result => { + expect(result).toEqual(2); + }) + .then(result => { + expect(result).toBeUndefined(); + }) + .then(result => { + expect(result).toBeUndefined(); + }); + }); + + test('multiple then returning different values', async () => { + expect.assertions(3); + + return resolvedSyncPromise(2) + .then(result => { + expect(result).toEqual(2); + return 3; + }) + .then(result => { + expect(result).toEqual(3); + return 4; + }) + .then(result => { + expect(result).toEqual(4); + }); + }); + + test('multiple then returning different SyncPromise', async () => { + expect.assertions(2); + + return resolvedSyncPromise(2) + .then(result => { + expect(result).toEqual(2); + return resolvedSyncPromise('yo'); + }) + .then(result => { + expect(result).toEqual('yo'); + }); + }); +}); + +describe('rejectedSyncPromise', () => { + test('simple', async () => { + expect.assertions(1); + + return new SyncPromise(resolve => { + resolve(42); + }).then(val => { + expect(val).toBe(42); + }); + }); + + test('simple error catching', async () => { + expect.assertions(1); + + const error = new Error('test error'); + + return rejectedSyncPromise(error).then( + () => { + throw new Error('THIS SHOULD NOT BE CALLED!'); + }, + val => { + expect(val).toBe(error); + }, + ); + }); + test('reject immediately and do not call then', async () => { + expect.assertions(1); + + return new SyncPromise((_, reject) => { + reject('test'); + }) + .then(_ => { + expect(true).toBeFalsy(); + }) + .then(null, reason => { + expect(reason).toBe('test'); + }); + }); + + test('reject', async () => { + expect.assertions(1); + + return new SyncPromise((_, reject) => { + reject('test'); + }).then(null, reason => { + expect(reason).toBe('test'); + }); + }); + + test('rejecting after first then', async () => { + expect.assertions(2); + + return resolvedSyncPromise(2) + .then(value => { + expect(value).toEqual(2); + return rejectedSyncPromise('wat'); + }) + .then(null, reason => { + expect(reason).toBe('wat'); + }); + }); +}); diff --git a/packages/core/test/mocks/transport.ts b/packages/core/test/mocks/transport.ts index c8ae84f80d64..ac4b2136c37e 100644 --- a/packages/core/test/mocks/transport.ts +++ b/packages/core/test/mocks/transport.ts @@ -1,9 +1,8 @@ import { createTransport } from '../../src/transports/base'; import type { Transport } from '../../src/types-hoist/transport'; -import { SyncPromise } from '../../src/utils/syncpromise'; async function sleep(delay: number): Promise { - return new SyncPromise(resolve => setTimeout(resolve, delay)); + return new Promise(resolve => setTimeout(resolve, delay)); } export function makeFakeTransport(delay: number = 2000): { @@ -15,13 +14,12 @@ export function makeFakeTransport(delay: number = 2000): { let sendCalled = 0; let sentCount = 0; const makeTransport = () => - createTransport({ recordDroppedEvent: () => undefined }, () => { + createTransport({ recordDroppedEvent: () => undefined }, async () => { sendCalled++; - return new SyncPromise(async res => { - await sleep(delay); - sentCount++; - res({}); - }); + await sleep(delay); + sentCount++; + return {}; }); + return { makeTransport, getSendCalled: () => sendCalled, getSentCount: () => sentCount, delay }; }