or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

async-utilities.mddata-processing.mdenvelopes.mdenvironment.mderror-handling.mdindex.mdinstrumentation.mdlogging.mdstack-processing.mdtype-guards.md

async-utilities.mddocs/

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.