Skip to content

Commit 61a0aa7

Browse files
committed
ref: Avoid some usage of SyncPromise where not needed
We make quite heavy use of it in event processing, not touching this for now, but in other places it should not be needed IMHO. fix transport using async refactor some more sync promise stuff fix node client fix some flushing stuff fix check oops small fixees fix tests fix lint fix to promise like small ref avoid special casing of no fetch implementation fetch stuff
1 parent 08c30f8 commit 61a0aa7

File tree

12 files changed

+109
-117
lines changed

12 files changed

+109
-117
lines changed

packages/browser/src/transports/fetch.ts

Lines changed: 15 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import type { Transport, TransportMakeRequestResponse, TransportRequest } from '@sentry/core';
2-
import { createTransport, rejectedSyncPromise } from '@sentry/core';
2+
import { createTransport } from '@sentry/core';
33
import { clearCachedImplementation, getNativeImplementation } from '@sentry-internal/browser-utils';
44
import type { WINDOW } from '../helpers';
55
import type { BrowserTransportOptions } from './types';
@@ -9,12 +9,12 @@ import type { BrowserTransportOptions } from './types';
99
*/
1010
export function makeFetchTransport(
1111
options: BrowserTransportOptions,
12-
nativeFetch: typeof WINDOW.fetch | undefined = getNativeImplementation('fetch'),
12+
nativeFetch: typeof WINDOW.fetch = getNativeImplementation('fetch'),
1313
): Transport {
1414
let pendingBodySize = 0;
1515
let pendingCount = 0;
1616

17-
function makeRequest(request: TransportRequest): PromiseLike<TransportMakeRequestResponse> {
17+
async function makeRequest(request: TransportRequest): Promise<TransportMakeRequestResponse> {
1818
const requestSize = request.body.length;
1919
pendingBodySize += requestSize;
2020
pendingCount++;
@@ -39,29 +39,23 @@ export function makeFetchTransport(
3939
...options.fetchOptions,
4040
};
4141

42-
if (!nativeFetch) {
43-
clearCachedImplementation('fetch');
44-
return rejectedSyncPromise('No fetch implementation available');
45-
}
46-
4742
try {
48-
// Note: We do not need to suppress tracing here, becasue we are using the native fetch, instead of our wrapped one.
49-
return nativeFetch(options.url, requestOptions).then(response => {
50-
pendingBodySize -= requestSize;
51-
pendingCount--;
52-
return {
53-
statusCode: response.status,
54-
headers: {
55-
'x-sentry-rate-limits': response.headers.get('X-Sentry-Rate-Limits'),
56-
'retry-after': response.headers.get('Retry-After'),
57-
},
58-
};
59-
});
43+
// Note: We do not need to suppress tracing here, because we are using the native fetch, instead of our wrapped one.
44+
const response = await nativeFetch(options.url, requestOptions);
45+
46+
return {
47+
statusCode: response.status,
48+
headers: {
49+
'x-sentry-rate-limits': response.headers.get('X-Sentry-Rate-Limits'),
50+
'retry-after': response.headers.get('Retry-After'),
51+
},
52+
};
6053
} catch (e) {
6154
clearCachedImplementation('fetch');
55+
throw e;
56+
} finally {
6257
pendingBodySize -= requestSize;
6358
pendingCount--;
64-
return rejectedSyncPromise(e);
6559
}
6660
}
6761

packages/browser/test/profiling/integration.test.ts

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
* @vitest-environment jsdom
33
*/
44

5-
import type { BrowserClient } from '@sentry/browser';
65
import * as Sentry from '@sentry/browser';
76
import { describe, expect, it, vi } from 'vitest';
87
import type { JSSelfProfile } from '../../src/profiling/jsSelfProfiling';
@@ -36,7 +35,7 @@ describe('BrowserProfilingIntegration', () => {
3635

3736
const flush = vi.fn().mockImplementation(() => Promise.resolve(true));
3837
const send = vi.fn().mockImplementation(() => Promise.resolve());
39-
Sentry.init({
38+
const client = Sentry.init({
4039
tracesSampleRate: 1,
4140
profilesSampleRate: 1,
4241
environment: 'test-environment',
@@ -50,13 +49,11 @@ describe('BrowserProfilingIntegration', () => {
5049
integrations: [Sentry.browserTracingIntegration(), Sentry.browserProfilingIntegration()],
5150
});
5251

53-
const client = Sentry.getClient<BrowserClient>();
54-
5552
const currentTransaction = Sentry.getActiveSpan();
5653
expect(currentTransaction).toBeDefined();
5754
expect(Sentry.spanToJSON(currentTransaction!).op).toBe('pageload');
5855
currentTransaction?.end();
59-
await client?.flush(1000);
56+
await client!.flush(1000);
6057

6158
expect(send).toHaveBeenCalledTimes(1);
6259

packages/browser/test/transports/fetch.test.ts

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import type { EventEnvelope, EventItem } from '@sentry/core';
22
import { createEnvelope, serializeEnvelope } from '@sentry/core';
3-
import type { Mock } from 'vitest';
3+
import { Mock, afterEach } from 'vitest';
44
import { describe, expect, it, vi } from 'vitest';
55
import { makeFetchTransport } from '../../src/transports/fetch';
66
import type { BrowserTransportOptions } from '../../src/transports/types';
@@ -29,7 +29,11 @@ class Headers {
2929
}
3030
}
3131

32-
describe('NewFetchTransport', () => {
32+
describe('fetchTransport', () => {
33+
afterEach(() => {
34+
vi.resetAllMocks();
35+
});
36+
3337
it('calls fetch with the given URL', async () => {
3438
const mockFetch = vi.fn(() =>
3539
Promise.resolve({
@@ -102,15 +106,28 @@ describe('NewFetchTransport', () => {
102106
});
103107
});
104108

105-
it('handles when `getNativetypeof window.fetchementation` is undefined', async () => {
109+
it('handles when native fetch implementation returns undefined', async () => {
106110
const mockFetch = vi.fn(() => undefined) as unknown as typeof window.fetch;
107111
const transport = makeFetchTransport(DEFAULT_FETCH_TRANSPORT_OPTIONS, mockFetch);
108112

109113
expect(mockFetch).toHaveBeenCalledTimes(0);
110-
await expect(() => transport.send(ERROR_ENVELOPE)).not.toThrow();
114+
await expect(() => transport.send(ERROR_ENVELOPE)).rejects.toThrow(
115+
"Cannot read properties of undefined (reading 'status')",
116+
);
111117
expect(mockFetch).toHaveBeenCalledTimes(1);
112118
});
113119

120+
it('handles when native fetch implementation is undefined', async () => {
121+
vi.mock('@sentry-internal/browser-utils', async importOriginal => ({
122+
...(await importOriginal()),
123+
getNativeImplementation: () => undefined,
124+
}));
125+
126+
const transport = makeFetchTransport(DEFAULT_FETCH_TRANSPORT_OPTIONS);
127+
128+
await expect(() => transport.send(ERROR_ENVELOPE)).rejects.toThrow('nativeFetch is not a function');
129+
});
130+
114131
it('correctly sets keepalive flag', async () => {
115132
const mockFetch = vi.fn(() =>
116133
Promise.resolve({

packages/bun/src/transports/index.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import type { BaseTransportOptions, Transport, TransportMakeRequestResponse, TransportRequest } from '@sentry/core';
2-
import { createTransport, rejectedSyncPromise, suppressTracing } from '@sentry/core';
2+
import { createTransport, suppressTracing } from '@sentry/core';
33

44
export interface BunTransportOptions extends BaseTransportOptions {
55
/** Custom headers for the transport. Used by the XHRTransport and FetchTransport */
@@ -30,7 +30,7 @@ export function makeFetchTransport(options: BunTransportOptions): Transport {
3030
});
3131
});
3232
} catch (e) {
33-
return rejectedSyncPromise(e);
33+
return Promise.reject(e);
3434
}
3535
}
3636

packages/core/src/client.ts

Lines changed: 45 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ import { parseSampleRate } from './utils/parseSampleRate';
4444
import { prepareEvent } from './utils/prepareEvent';
4545
import { reparentChildSpans, shouldIgnoreSpan } from './utils/should-ignore-span';
4646
import { getActiveSpan, showSpanDropWarning, spanToTraceContext } from './utils/spanUtils';
47-
import { rejectedSyncPromise, resolvedSyncPromise, SyncPromise } from './utils/syncpromise';
47+
import { rejectedSyncPromise } from './utils/syncpromise';
4848
import { convertSpanJsonToTransactionEvent, convertTransactionEventToSpanJson } from './utils/transactionEvent';
4949

5050
const ALREADY_SEEN_ERROR = "Not capturing exception because it's already been captured.";
@@ -316,16 +316,19 @@ export abstract class Client<O extends ClientOptions = ClientOptions> {
316316
* @returns A promise that will resolve with `true` if all events are sent before the timeout, or `false` if there are
317317
* still events in the queue when the timeout is reached.
318318
*/
319-
public flush(timeout?: number): PromiseLike<boolean> {
319+
// @ts-expect-error - PromiseLike is a subset of Promise
320+
public async flush(timeout?: number): PromiseLike<boolean> {
320321
const transport = this._transport;
321-
if (transport) {
322-
this.emit('flush');
323-
return this._isClientDoneProcessing(timeout).then(clientFinished => {
324-
return transport.flush(timeout).then(transportFlushed => clientFinished && transportFlushed);
325-
});
326-
} else {
327-
return resolvedSyncPromise(true);
322+
if (!transport) {
323+
return Promise.resolve(true);
328324
}
325+
326+
this.emit('flush');
327+
328+
const clientFinished = await this._isClientDoneProcessing(timeout);
329+
const transportFlushed = await transport.flush(timeout);
330+
331+
return clientFinished && transportFlushed;
329332
}
330333

331334
/**
@@ -336,12 +339,12 @@ export abstract class Client<O extends ClientOptions = ClientOptions> {
336339
* @returns {Promise<boolean>} A promise which resolves to `true` if the flush completes successfully before the timeout, or `false` if
337340
* it doesn't.
338341
*/
339-
public close(timeout?: number): PromiseLike<boolean> {
340-
return this.flush(timeout).then(result => {
341-
this.getOptions().enabled = false;
342-
this.emit('close');
343-
return result;
344-
});
342+
// @ts-expect-error - PromiseLike is a subset of Promise
343+
public async close(timeout?: number): PromiseLike<boolean> {
344+
const result = await this.flush(timeout);
345+
this.getOptions().enabled = false;
346+
this.emit('close');
347+
return result;
345348
}
346349

347350
/**
@@ -415,10 +418,9 @@ export abstract class Client<O extends ClientOptions = ClientOptions> {
415418
env = addItemToEnvelope(env, createAttachmentEnvelopeItem(attachment));
416419
}
417420

418-
const promise = this.sendEnvelope(env);
419-
if (promise) {
420-
promise.then(sendResponse => this.emit('afterSendEvent', event, sendResponse), null);
421-
}
421+
// sendEnvelope should not throw
422+
// eslint-disable-next-line @typescript-eslint/no-floating-promises
423+
this.sendEnvelope(env).then(sendResponse => this.emit('afterSendEvent', event, sendResponse));
422424
}
423425

424426
/**
@@ -873,19 +875,21 @@ export abstract class Client<O extends ClientOptions = ClientOptions> {
873875
/**
874876
* Send an envelope to Sentry.
875877
*/
876-
public sendEnvelope(envelope: Envelope): PromiseLike<TransportMakeRequestResponse> {
878+
// @ts-expect-error - PromiseLike is a subset of Promise
879+
public async sendEnvelope(envelope: Envelope): PromiseLike<TransportMakeRequestResponse> {
877880
this.emit('beforeEnvelope', envelope);
878881

879-
if (this._isEnabled() && this._transport) {
880-
return this._transport.send(envelope).then(null, reason => {
881-
DEBUG_BUILD && debug.error('Error while sending envelope:', reason);
882-
return reason;
883-
});
882+
if (!this._isEnabled() || !this._transport) {
883+
DEBUG_BUILD && debug.error('Transport disabled');
884+
return {};
884885
}
885886

886-
DEBUG_BUILD && debug.error('Transport disabled');
887-
888-
return resolvedSyncPromise({});
887+
try {
888+
return await this._transport.send(envelope);
889+
} catch (reason) {
890+
DEBUG_BUILD && debug.error('Error while sending envelope:', reason);
891+
return {};
892+
}
889893
}
890894

891895
/* eslint-enable @typescript-eslint/unified-signatures */
@@ -940,24 +944,20 @@ export abstract class Client<O extends ClientOptions = ClientOptions> {
940944
* @returns A promise which will resolve to `true` if processing is already done or finishes before the timeout, and
941945
* `false` otherwise
942946
*/
943-
protected _isClientDoneProcessing(timeout?: number): PromiseLike<boolean> {
944-
return new SyncPromise(resolve => {
945-
let ticked: number = 0;
946-
const tick: number = 1;
947+
protected async _isClientDoneProcessing(timeout?: number): Promise<boolean> {
948+
let ticked = 0;
947949

948-
const interval = setInterval(() => {
949-
if (this._numProcessing == 0) {
950-
clearInterval(interval);
951-
resolve(true);
952-
} else {
953-
ticked += tick;
954-
if (timeout && ticked >= timeout) {
955-
clearInterval(interval);
956-
resolve(false);
957-
}
958-
}
959-
}, tick);
960-
});
950+
// if no timeout is provided, we wait "forever" until everything is processed
951+
while (!timeout || ticked < timeout) {
952+
await new Promise(resolve => setTimeout(resolve, 1));
953+
954+
if (!this._numProcessing) {
955+
return true;
956+
}
957+
ticked++;
958+
}
959+
960+
return false;
961961
}
962962

963963
/** Determines whether this SDK is enabled and a transport is present. */

packages/core/src/transports/base.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@ import {
1616
} from '../utils/envelope';
1717
import { type PromiseBuffer, makePromiseBuffer, SENTRY_BUFFER_FULL_ERROR } from '../utils/promisebuffer';
1818
import { type RateLimits, isRateLimited, updateRateLimits } from '../utils/ratelimit';
19-
import { resolvedSyncPromise } from '../utils/syncpromise';
2019

2120
export const DEFAULT_TRANSPORT_BUFFER_SIZE = 64;
2221

@@ -51,7 +50,7 @@ export function createTransport(
5150

5251
// Skip sending if envelope is empty after filtering out rate limited events
5352
if (filteredEnvelopeItems.length === 0) {
54-
return resolvedSyncPromise({});
53+
return Promise.resolve({});
5554
}
5655

5756
const filteredEnvelope: Envelope = createEnvelope(envelope[0], filteredEnvelopeItems as (typeof envelope)[1]);
@@ -87,7 +86,7 @@ export function createTransport(
8786
if (error === SENTRY_BUFFER_FULL_ERROR) {
8887
DEBUG_BUILD && debug.error('Skipped sending event because buffer is full.');
8988
recordEnvelopeLoss('queue_overflow');
90-
return resolvedSyncPromise({});
89+
return Promise.resolve({});
9190
} else {
9291
throw error;
9392
}

packages/core/test/lib/client.test.ts

Lines changed: 4 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -2200,11 +2200,7 @@ describe('Client', () => {
22002200
client.on('afterSendEvent', callback);
22012201

22022202
client.sendEvent(errorEvent);
2203-
vi.runAllTimers();
2204-
// Wait for two ticks
2205-
// note that for whatever reason, await new Promise(resolve => setTimeout(resolve, 0)) causes the test to hang
2206-
await undefined;
2207-
await undefined;
2203+
await vi.runAllTimersAsync();
22082204

22092205
expect(mockSend).toBeCalledTimes(1);
22102206
expect(callback).toBeCalledTimes(1);
@@ -2228,11 +2224,7 @@ describe('Client', () => {
22282224
client.on('afterSendEvent', callback);
22292225

22302226
client.sendEvent(transactionEvent);
2231-
vi.runAllTimers();
2232-
// Wait for two ticks
2233-
// note that for whatever reason, await new Promise(resolve => setTimeout(resolve, 0)) causes the test to hang
2234-
await undefined;
2235-
await undefined;
2227+
await vi.runAllTimersAsync();
22362228

22372229
expect(mockSend).toBeCalledTimes(1);
22382230
expect(callback).toBeCalledTimes(1);
@@ -2260,15 +2252,11 @@ describe('Client', () => {
22602252
client.on('afterSendEvent', callback);
22612253

22622254
client.sendEvent(errorEvent);
2263-
vi.runAllTimers();
2264-
// Wait for two ticks
2265-
// note that for whatever reason, await new Promise(resolve => setTimeout(resolve, 0)) causes the test to hang
2266-
await undefined;
2267-
await undefined;
2255+
await vi.runAllTimersAsync();
22682256

22692257
expect(mockSend).toBeCalledTimes(1);
22702258
expect(callback).toBeCalledTimes(1);
2271-
expect(callback).toBeCalledWith(errorEvent, 'send error');
2259+
expect(callback).toBeCalledWith(errorEvent, {});
22722260
});
22732261

22742262
it('passes the response to the hook', async () => {

packages/deno/src/integrations/deno-cron.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,11 @@ const _denoCronIntegration = (() => {
1515
return {
1616
name: INTEGRATION_NAME,
1717
setupOnce() {
18-
// eslint-disable-next-line deprecation/deprecation
1918
if (!Deno.cron) {
2019
// The cron API is not available in this Deno version use --unstable flag!
2120
return;
2221
}
2322

24-
// eslint-disable-next-line deprecation/deprecation
2523
Deno.cron = new Proxy(Deno.cron, {
2624
apply(target, thisArg, argArray: CronParams) {
2725
const [monitorSlug, schedule, opt1, opt2] = argArray;

0 commit comments

Comments
 (0)