---
title: "Async Operations"
description: "Asynchronous programming patterns with the TraceRTM JavaScript SDK"
---

# Async Operations

The TraceRTM JavaScript SDK is fully asynchronous, using Promises and async/await for all API operations.

## Overview

All SDK methods that communicate with the API return Promises. This allows you to:

- Use modern async/await syntax
- Chain operations with `.then()` and `.catch()`
- Handle errors gracefully
- Execute operations concurrently
- Build responsive applications

---

## Basic Async Patterns

### Using async/await

The recommended approach for most applications:

```typescript
import { TraceRTMClient } from "@tracertm/sdk";

const client = new TraceRTMClient({
  apiKey: process.env.TRACERTM_API_KEY,
});

async function main() {
  // Sequential operations
  const project = await client.projects.get("proj_123");
  const items = await client.items.list({ projectId: project.id });

  console.log(`Project: ${project.name}`);
  console.log(`Items: ${items.total}`);
}

main().catch(console.error);
```

### Using Promises

For environments without async/await support:

```typescript
client.projects
  .get("proj_123")
  .then((project) => {
    console.log(`Project: ${project.name}`);
    return client.items.list({ projectId: project.id });
  })
  .then((items) => {
    console.log(`Items: ${items.total}`);
  })
  .catch((error) => {
    console.error("Error:", error.message);
  });
```

---

## Concurrent Operations

### Parallel Requests with Promise.all

Execute multiple independent requests simultaneously:

```typescript
async function loadDashboardData(projectId: string) {
  const [project, items, coverage, stats] = await Promise.all([
    client.projects.get(projectId),
    client.items.list({ projectId, limit: 10 }),
    client.reports.coverage({
      projectId,
      sourceType: "requirement",
      targetType: "test_case",
    }),
    client.projects.getStats(projectId),
  ]);

  return { project, items, coverage, stats };
}
```

### Parallel with Error Handling

Use `Promise.allSettled` when you want all results regardless of failures:

```typescript
async function loadMultipleProjects(projectIds: string[]) {
  const results = await Promise.allSettled(
    projectIds.map((id) => client.projects.get(id))
  );

  const succeeded = results
    .filter((r): r is PromiseFulfilledResult<Project> => r.status === "fulfilled")
    .map((r) => r.value);

  const failed = results
    .filter((r): r is PromiseRejectedResult => r.status === "rejected")
    .map((r) => r.reason);

  return { succeeded, failed };
}
```

### Race Conditions

Get the first successful result:

```typescript
async function getFromMultipleSources(itemId: string) {
  // Try multiple sources, use first response
  const result = await Promise.race([
    client.items.get(itemId),
    fetchFromCache(itemId),
  ]);

  return result;
}
```

---

## Controlled Concurrency

### Limiting Concurrent Requests

Prevent overwhelming the API with a concurrency limiter:

```typescript
class ConcurrencyLimiter {
  private running = 0;
  private queue: Array<() => void> = [];

  constructor(private maxConcurrent: number) {}

  async run<T>(fn: () => Promise<T>): Promise<T> {
    while (this.running >= this.maxConcurrent) {
      await new Promise<void>((resolve) => this.queue.push(resolve));
    }

    this.running++;
    try {
      return await fn();
    } finally {
      this.running--;
      const next = this.queue.shift();
      if (next) next();
    }
  }
}

// Usage
const limiter = new ConcurrencyLimiter(5);

async function fetchAllItems(itemIds: string[]) {
  return Promise.all(
    itemIds.map((id) => limiter.run(() => client.items.get(id)))
  );
}
```

### Batch Processing

Process items in batches:

```typescript
async function processBatches<T, R>(
  items: T[],
  processor: (item: T) => Promise<R>,
  batchSize: number = 10
): Promise<R[]> {
  const results: R[] = [];

  for (let i = 0; i < items.length; i += batchSize) {
    const batch = items.slice(i, i + batchSize);
    const batchResults = await Promise.all(batch.map(processor));
    results.push(...batchResults);

    // Optional: Add delay between batches
    if (i + batchSize < items.length) {
      await new Promise((resolve) => setTimeout(resolve, 100));
    }
  }

  return results;
}

// Usage
const itemIds = ["item_1", "item_2", "item_3", /* ... */];
const items = await processBatches(
  itemIds,
  (id) => client.items.get(id),
  5 // Process 5 at a time
);
```

---

## Sequential Operations

### Chained Operations

When operations depend on each other:

```typescript
async function createProjectWithItems() {
  // Step 1: Create project
  const project = await client.projects.create({
    name: "New Project",
    key: "NP",
  });

  // Step 2: Create requirements (depends on project)
  const req = await client.items.create({
    projectId: project.id,
    type: "requirement",
    title: "First Requirement",
  });

  // Step 3: Create test case (depends on project)
  const test = await client.items.create({
    projectId: project.id,
    type: "test_case",
    title: "First Test",
  });

  // Step 4: Link them (depends on both items)
  await client.links.create({
    sourceId: req.id,
    targetId: test.id,
    type: "verified_by",
  });

  return { project, req, test };
}
```

### Waterfall Pattern

Execute async operations in sequence:

```typescript
async function waterfall<T>(
  initial: T,
  operations: Array<(value: T) => Promise<T>>
): Promise<T> {
  let result = initial;

  for (const operation of operations) {
    result = await operation(result);
  }

  return result;
}

// Usage
const finalItem = await waterfall(
  await client.items.get("item_123"),
  [
    async (item) => client.items.update(item.id, { status: "in_review" }),
    async (item) => client.items.update(item.id, { status: "approved" }),
    async (item) => client.items.update(item.id, { tags: [...item.tags, "released"] }),
  ]
);
```

---

## Async Iteration

### Paginating Through Results

Iterate through all pages of results:

```typescript
async function* paginateItems(projectId: string, pageSize: number = 100) {
  let offset = 0;

  while (true) {
    const response = await client.items.list({
      projectId,
      limit: pageSize,
      offset,
    });

    for (const item of response.data) {
      yield item;
    }

    if (!response.hasMore) {
      break;
    }

    offset += pageSize;
  }
}

// Usage
for await (const item of paginateItems("proj_123")) {
  console.log(item.title);
}
```

### Collect All Items

Get all items as an array:

```typescript
async function getAllItems(projectId: string): Promise<Item[]> {
  const items: Item[] = [];

  for await (const item of paginateItems(projectId)) {
    items.push(item);
  }

  return items;
}
```

---

## Error Handling

### Try/Catch with async/await

```typescript
async function safeGetItem(itemId: string) {
  try {
    const item = await client.items.get(itemId);
    return { success: true, data: item };
  } catch (error) {
    if (error instanceof NotFoundError) {
      return { success: false, error: "Item not found" };
    }
    throw error; // Re-throw unexpected errors
  }
}
```

### Promise Rejection Handling

```typescript
client.items
  .get("item_123")
  .then((item) => {
    console.log(item.title);
  })
  .catch((error) => {
    if (error instanceof NotFoundError) {
      console.log("Item not found");
    } else {
      console.error("Unexpected error:", error);
    }
  });
```

### Global Unhandled Rejection Handler

```typescript
// Node.js
process.on("unhandledRejection", (reason, promise) => {
  console.error("Unhandled Rejection at:", promise, "reason:", reason);
});

// Browser
window.addEventListener("unhandledrejection", (event) => {
  console.error("Unhandled rejection:", event.reason);
});
```

---

## Timeouts and Cancellation

### With AbortController

```typescript
async function fetchWithTimeout(itemId: string, timeoutMs: number) {
  const controller = new AbortController();
  const timeout = setTimeout(() => controller.abort(), timeoutMs);

  try {
    // Note: SDK must support abort signal
    const item = await client.items.get(itemId, {
      signal: controller.signal,
    });
    return item;
  } finally {
    clearTimeout(timeout);
  }
}
```

### Promise Timeout Wrapper

```typescript
function withTimeout<T>(promise: Promise<T>, ms: number): Promise<T> {
  const timeout = new Promise<never>((_, reject) => {
    setTimeout(() => reject(new Error(`Timeout after ${ms}ms`)), ms);
  });

  return Promise.race([promise, timeout]);
}

// Usage
try {
  const item = await withTimeout(client.items.get("item_123"), 5000);
} catch (error) {
  if (error.message.includes("Timeout")) {
    console.log("Request timed out");
  }
}
```

---

## Retry Logic

### Automatic Retry with Exponential Backoff

```typescript
async function withRetry<T>(
  fn: () => Promise<T>,
  options: {
    maxRetries?: number;
    baseDelay?: number;
    maxDelay?: number;
  } = {}
): Promise<T> {
  const { maxRetries = 3, baseDelay = 1000, maxDelay = 10000 } = options;

  let lastError: Error;

  for (let attempt = 0; attempt <= maxRetries; attempt++) {
    try {
      return await fn();
    } catch (error) {
      lastError = error as Error;

      // Don't retry certain errors
      if (error instanceof ValidationError || error instanceof NotFoundError) {
        throw error;
      }

      if (attempt < maxRetries) {
        const delay = Math.min(baseDelay * Math.pow(2, attempt), maxDelay);
        await new Promise((resolve) => setTimeout(resolve, delay));
      }
    }
  }

  throw lastError!;
}

// Usage
const item = await withRetry(() => client.items.get("item_123"), {
  maxRetries: 3,
  baseDelay: 1000,
});
```

---

## Best Practices

### 1. Always Handle Rejections

```typescript
// Bad - unhandled rejection
client.items.get("item_123");

// Good - handle the promise
client.items.get("item_123").catch(console.error);

// Better - use async/await with try/catch
try {
  const item = await client.items.get("item_123");
} catch (error) {
  console.error(error);
}
```

### 2. Avoid Mixing Patterns

```typescript
// Bad - mixing async/await and .then()
async function mixed() {
  const project = await client.projects.get("proj_123");
  client.items.list({ projectId: project.id }).then(/* ... */);
}

// Good - consistent pattern
async function consistent() {
  const project = await client.projects.get("proj_123");
  const items = await client.items.list({ projectId: project.id });
}
```

### 3. Use Concurrent Operations When Possible

```typescript
// Slow - sequential
const project = await client.projects.get("proj_123");
const items = await client.items.list({ projectId: "proj_123" });
const coverage = await client.reports.coverage({ projectId: "proj_123" });

// Fast - concurrent
const [project, items, coverage] = await Promise.all([
  client.projects.get("proj_123"),
  client.items.list({ projectId: "proj_123" }),
  client.reports.coverage({ projectId: "proj_123" }),
]);
```

---

## Related Documentation

- [Promises](/docs/api-reference/sdks/javascript/promises) - Promise patterns
- [Async/Await](/docs/api-reference/sdks/javascript/async-await) - async/await guide
- [Error Handling](/docs/api-reference/sdks/javascript/error-handling) - Error handling
