0
# Promise & Async Utilities
1
2
**DEPRECATED**: Import all functions from `@sentry/core` instead of `@sentry/utils`.
3
4
Synchronous promise implementation and promise buffer management for reliable async operations and controlled concurrency.
5
6
## Capabilities
7
8
### SyncPromise Class
9
10
Synchronous promise implementation that executes immediately without microtask scheduling.
11
12
```typescript { .api }
13
/**
14
* Synchronous promise implementation that executes immediately
15
* @template T - Type of the resolved value
16
*/
17
class SyncPromise<T> implements PromiseLike<T> {
18
/**
19
* Creates a new SyncPromise
20
* @param executor - Function that initializes the promise
21
*/
22
constructor(
23
executor: (
24
resolve: (value: T | PromiseLike<T>) => void,
25
reject: (reason?: any) => void
26
) => void
27
);
28
29
/**
30
* Attaches callbacks for resolution and/or rejection
31
* @param onfulfilled - Callback for successful resolution
32
* @param onrejected - Callback for rejection
33
* @returns New SyncPromise with transformed value
34
*/
35
then<TResult1 = T, TResult2 = never>(
36
onfulfilled?: ((value: T) => TResult1 | PromiseLike<TResult1>) | null,
37
onrejected?: ((reason: any) => TResult2 | PromiseLike<TResult2>) | null,
38
): SyncPromise<TResult1 | TResult2>;
39
40
/**
41
* Attaches a callback for rejection
42
* @param onrejected - Callback for rejection
43
* @returns New SyncPromise
44
*/
45
catch<TResult = never>(
46
onrejected?: ((reason: any) => TResult | PromiseLike<TResult>) | null,
47
): SyncPromise<T | TResult>;
48
49
/**
50
* Returns a resolved SyncPromise with the given value
51
* @param value - Value to resolve with
52
* @returns Resolved SyncPromise
53
*/
54
static resolve<T>(value: T | PromiseLike<T>): SyncPromise<T>;
55
56
/**
57
* Returns a rejected SyncPromise with the given reason
58
* @param reason - Reason for rejection
59
* @returns Rejected SyncPromise
60
*/
61
static reject<T = never>(reason?: any): SyncPromise<T>;
62
}
63
```
64
65
### SyncPromise Factory Functions
66
67
Convenient factory functions for creating resolved and rejected SyncPromises.
68
69
```typescript { .api }
70
/**
71
* Creates a resolved SyncPromise with the given value
72
* @param value - Value to resolve with
73
* @returns Resolved SyncPromise
74
*/
75
function resolvedSyncPromise<T>(value: T): SyncPromise<T>;
76
77
/**
78
* Creates a rejected SyncPromise with the given reason
79
* @param reason - Reason for rejection
80
* @returns Rejected SyncPromise
81
*/
82
function rejectedSyncPromise<T = never>(reason: any): SyncPromise<T>;
83
```
84
85
**Usage Examples:**
86
87
```typescript
88
import { SyncPromise, resolvedSyncPromise, rejectedSyncPromise } from "@sentry/core";
89
90
// Basic SyncPromise usage - executes immediately
91
const syncPromise = new SyncPromise<number>((resolve, reject) => {
92
console.log('Executing immediately!'); // Logs immediately, not on next tick
93
resolve(42);
94
});
95
96
syncPromise.then(value => {
97
console.log('Value:', value); // Logs immediately after resolve
98
});
99
100
// Factory function usage
101
const resolved = resolvedSyncPromise('success');
102
const rejected = rejectedSyncPromise(new Error('failed'));
103
104
// Chaining works like regular promises
105
const result = resolvedSyncPromise(10)
106
.then(x => x * 2)
107
.then(x => x + 5)
108
.catch(err => console.error(err));
109
110
console.log(result); // SyncPromise, but value is computed immediately
111
112
// Converting async operations to sync for testing
113
function mockAsyncOperation(shouldFail: boolean): SyncPromise<string> {
114
if (shouldFail) {
115
return rejectedSyncPromise(new Error('Operation failed'));
116
}
117
return resolvedSyncPromise('Operation succeeded');
118
}
119
```
120
121
### Promise Buffer
122
123
Managed buffer for controlling concurrent promise execution with limits.
124
125
```typescript { .api }
126
/**
127
* Buffer that manages concurrent promise execution
128
* @template T - Type of promise results
129
*/
130
interface PromiseBuffer<T> {
131
/** Array of currently running promises */
132
readonly $: Array<PromiseLike<T>>;
133
134
/**
135
* Adds a task to the buffer, respecting concurrency limits
136
* @param taskProducer - Function that creates the promise when ready to execute
137
* @returns Promise that resolves when the task completes
138
*/
139
add(taskProducer: () => PromiseLike<T>): PromiseLike<T>;
140
141
/**
142
* Waits for all currently running promises to complete
143
* @param timeout - Optional timeout in milliseconds
144
* @returns Promise that resolves to true if all tasks completed, false if timeout
145
*/
146
drain(timeout?: number): PromiseLike<boolean>;
147
}
148
149
/**
150
* Creates a new promise buffer with optional concurrency limit
151
* @param limit - Maximum number of concurrent promises (default: 30)
152
* @returns New promise buffer instance
153
*/
154
function makePromiseBuffer<T>(limit?: number): PromiseBuffer<T>;
155
```
156
157
**Usage Examples:**
158
159
```typescript
160
import { makePromiseBuffer } from "@sentry/core";
161
162
// Create buffer with concurrency limit
163
const buffer = makePromiseBuffer<string>(3); // Max 3 concurrent operations
164
165
// Add tasks to buffer - they'll execute when slots are available
166
const tasks = Array.from({ length: 10 }, (_, i) =>
167
buffer.add(() => fetchData(`item-${i}`))
168
);
169
170
// Wait for all tasks to complete
171
Promise.all(tasks).then(results => {
172
console.log('All tasks completed:', results);
173
});
174
175
// Example: Processing file uploads with concurrency control
176
class FileUploader {
177
private uploadBuffer = makePromiseBuffer<UploadResult>(5);
178
179
async uploadFiles(files: File[]): Promise<UploadResult[]> {
180
const uploadTasks = files.map(file =>
181
this.uploadBuffer.add(() => this.uploadSingleFile(file))
182
);
183
184
return Promise.all(uploadTasks);
185
}
186
187
private async uploadSingleFile(file: File): Promise<UploadResult> {
188
// Simulate file upload
189
const formData = new FormData();
190
formData.append('file', file);
191
192
const response = await fetch('/upload', {
193
method: 'POST',
194
body: formData
195
});
196
197
return response.json();
198
}
199
200
async waitForAllUploads(timeout = 30000): Promise<boolean> {
201
return this.uploadBuffer.drain(timeout);
202
}
203
}
204
```
205
206
### Watchdog Timer
207
208
Utility for creating timeout mechanisms and operation monitoring.
209
210
```typescript { .api }
211
/**
212
* Creates a watchdog timer that can be used to timeout operations
213
* @param callback - Function to call when timer expires
214
* @param delay - Delay in milliseconds before calling callback
215
* @returns Function to cancel the timer
216
*/
217
function watchdogTimer(callback: () => void, delay: number): () => void;
218
```
219
220
**Usage Examples:**
221
222
```typescript
223
import { watchdogTimer } from "@sentry/core";
224
225
// Basic timeout functionality
226
function withTimeout<T>(promise: Promise<T>, timeoutMs: number): Promise<T> {
227
return new Promise((resolve, reject) => {
228
const cancel = watchdogTimer(() => {
229
reject(new Error(`Operation timed out after ${timeoutMs}ms`));
230
}, timeoutMs);
231
232
promise
233
.then(resolve)
234
.catch(reject)
235
.finally(cancel); // Cancel timer when promise settles
236
});
237
}
238
239
// Usage with async operations
240
async function fetchWithTimeout(url: string): Promise<Response> {
241
const fetchPromise = fetch(url);
242
return withTimeout(fetchPromise, 5000); // 5 second timeout
243
}
244
245
// Periodic health checks
246
class HealthMonitor {
247
private cancelWatchdog?: () => void;
248
249
startMonitoring(intervalMs: number) {
250
const scheduleNext = () => {
251
this.cancelWatchdog = watchdogTimer(() => {
252
this.performHealthCheck()
253
.then(() => scheduleNext()) // Schedule next check
254
.catch(err => console.error('Health check failed:', err));
255
}, intervalMs);
256
};
257
258
scheduleNext();
259
}
260
261
stopMonitoring() {
262
this.cancelWatchdog?.();
263
}
264
265
private async performHealthCheck(): Promise<void> {
266
// Health check logic
267
const response = await fetch('/health');
268
if (!response.ok) {
269
throw new Error(`Health check failed: ${response.status}`);
270
}
271
}
272
}
273
```
274
275
## Async Patterns
276
277
### Converting Regular Promises to SyncPromises
278
279
Pattern for testing and synchronous execution:
280
281
```typescript
282
import { SyncPromise, resolvedSyncPromise, rejectedSyncPromise } from "@sentry/core";
283
284
function toSyncPromise<T>(promise: Promise<T>): SyncPromise<T> {
285
// Note: This breaks the asynchronous nature - use carefully
286
let result: T;
287
let error: any;
288
let isResolved = false;
289
let isRejected = false;
290
291
promise
292
.then(value => {
293
result = value;
294
isResolved = true;
295
})
296
.catch(err => {
297
error = err;
298
isRejected = true;
299
});
300
301
// Warning: This is a synchronous check and won't work for truly async operations
302
if (isResolved) {
303
return resolvedSyncPromise(result);
304
} else if (isRejected) {
305
return rejectedSyncPromise(error);
306
} else {
307
// For truly async operations, this approach won't work
308
throw new Error('Cannot convert async promise to sync');
309
}
310
}
311
```
312
313
### Controlled Batch Processing
314
315
Using promise buffers for controlled batch processing:
316
317
```typescript
318
import { makePromiseBuffer } from "@sentry/core";
319
320
class BatchProcessor<T, R> {
321
private buffer = makePromiseBuffer<R>(10);
322
323
async processBatch(items: T[], processor: (item: T) => Promise<R>): Promise<R[]> {
324
const tasks = items.map(item =>
325
this.buffer.add(() => processor(item))
326
);
327
328
return Promise.all(tasks);
329
}
330
331
async processWithRetry<T, R>(
332
items: T[],
333
processor: (item: T) => Promise<R>,
334
maxRetries = 3
335
): Promise<Array<R | Error>> {
336
const results: Array<R | Error> = [];
337
338
for (const item of items) {
339
const processWithRetry = async (): Promise<R> => {
340
let lastError: Error;
341
342
for (let attempt = 0; attempt <= maxRetries; attempt++) {
343
try {
344
return await processor(item);
345
} catch (error) {
346
lastError = error as Error;
347
if (attempt < maxRetries) {
348
await new Promise(resolve => setTimeout(resolve, 1000 * Math.pow(2, attempt)));
349
}
350
}
351
}
352
353
throw lastError!;
354
};
355
356
try {
357
const result = await this.buffer.add(processWithRetry);
358
results.push(result);
359
} catch (error) {
360
results.push(error as Error);
361
}
362
}
363
364
return results;
365
}
366
}
367
```
368
369
### Circuit Breaker Pattern
370
371
Using watchdog timers for circuit breaker implementation:
372
373
```typescript
374
import { watchdogTimer } from "@sentry/core";
375
376
class CircuitBreaker {
377
private failureCount = 0;
378
private lastFailureTime = 0;
379
private state: 'CLOSED' | 'OPEN' | 'HALF_OPEN' = 'CLOSED';
380
381
constructor(
382
private failureThreshold = 5,
383
private timeout = 60000, // 1 minute
384
private retryTimeout = 10000 // 10 seconds
385
) {}
386
387
async execute<T>(operation: () => Promise<T>): Promise<T> {
388
if (this.state === 'OPEN') {
389
if (Date.now() - this.lastFailureTime < this.retryTimeout) {
390
throw new Error('Circuit breaker is OPEN');
391
} else {
392
this.state = 'HALF_OPEN';
393
}
394
}
395
396
try {
397
const result = await this.executeWithTimeout(operation);
398
this.onSuccess();
399
return result;
400
} catch (error) {
401
this.onFailure();
402
throw error;
403
}
404
}
405
406
private executeWithTimeout<T>(operation: () => Promise<T>): Promise<T> {
407
return new Promise((resolve, reject) => {
408
const cancel = watchdogTimer(() => {
409
reject(new Error('Operation timed out'));
410
}, this.timeout);
411
412
operation()
413
.then(resolve)
414
.catch(reject)
415
.finally(cancel);
416
});
417
}
418
419
private onSuccess(): void {
420
this.failureCount = 0;
421
this.state = 'CLOSED';
422
}
423
424
private onFailure(): void {
425
this.failureCount++;
426
this.lastFailureTime = Date.now();
427
428
if (this.failureCount >= this.failureThreshold) {
429
this.state = 'OPEN';
430
}
431
}
432
}
433
```
434
435
## Types
436
437
```typescript { .api }
438
interface UploadResult {
439
filename: string;
440
size: number;
441
url: string;
442
success: boolean;
443
}
444
```
445
446
**Migration Note**: All promise utilities have been moved from `@sentry/utils` to `@sentry/core`. Update your imports accordingly.