Claude
Skills
Sign in
Back

deno-patterns

Included with Lifetime
$97 forever

Modern TypeScript patterns and migration guidance for Deno: resource management with 'using', async generators, error handling, and Web Standard APIs. Use when migrating from Node.js, implementing cleanup logic, or learning modern JS/TS patterns.

Backend & APIs

What this skill does


# Deno Modern Patterns & Migration

## When to Use This Skill

Use this skill when:
- Migrating from Node.js to Deno
- Learning modern JavaScript/TypeScript patterns
- Implementing resource management
- Working with timers, polling, or cleanup logic
- Handling errors in Deno
- Choosing between old and new patterns

**Note:** Always read `deno-core.md` first for essential configuration and practices.

---

## Resource Management with `using`

### The Problem with Manual Cleanup

**Old Pattern:**

```javascript
// Manual timer cleanup - error-prone
const id = setInterval(() => {
  console.log("Tick!");
}, 1000);

// Later, cleanup (easy to forget!)
clearInterval(id);
```

**Problems:**
- Resource leaks if cleanup is missed
- Not exception-safe
- Resource not tied to lexical scope
- Cleanup code separated from acquisition

### The `using` Statement (Deno >= v2.4)

**New Pattern:**

```typescript
// Automatic, exception-safe cleanup
class Timer {
  #handle: number;

  constructor(cb: () => void, ms: number) {
    this.#handle = setInterval(cb, ms);
  }

  [Symbol.dispose]() {
    clearInterval(this.#handle);
    console.log("Timer disposed");
  }
}

using timer = new Timer(() => {
  console.log("Tick!");
}, 1000);
// Timer automatically cleaned up at end of scope
```

**Benefits:**
- Automatic cleanup at scope exit
- Exception-safe (cleanup happens even if errors occur)
- Follows RAII (Resource Acquisition Is Initialization) principles
- Prevents resource leaks in complex control flows
- Composable with multiple `using` declarations

### Async Resource Management

For async cleanup, use `await using` with `Symbol.asyncDispose`:

```typescript
class DatabaseConnection {
  #conn: Connection;

  constructor(conn: Connection) {
    this.#conn = conn;
  }

  async [Symbol.asyncDispose]() {
    await this.#conn.close();
    console.log("Database connection closed");
  }
}

// Automatically closes when scope exits
await using db = new DatabaseConnection(conn);
await db.query("SELECT * FROM users");
// Connection closed here, even if query throws
```

### When to Use `using`

Use `using` for any resource that needs cleanup:
- Timers (setInterval, setTimeout)
- File handles
- Database connections
- Network connections
- Locks and mutexes
- Any object with teardown logic

---

## Async Iteration Over Polling

### The Problem with Traditional Polling

**Old Pattern:**

```javascript
// Traditional polling - not cancelable, drift-prone
let running = true;

function poll() {
  if (!running) return;
  // ...check something...
  setTimeout(poll, 1000);
}

poll();
running = false;  // Unreliable cancellation
```

**Problems:**
- Timer drift accumulates over time
- Race conditions with cancellation flag
- Hard to compose or integrate with other async code
- Not part of the structured concurrency model

### Async Generators for Polling

**New Pattern:**

```typescript
// Async generator - naturally cancelable
async function* interval(ms: number) {
  while (true) {
    yield;
    await new Promise((r) => setTimeout(r, ms));
  }
}

// Usage with for-await-of
for await (const _ of interval(1000)) {
  // ...do work...
  if (shouldStop()) break;  // Clean, immediate cancellation
}
```

**Benefits:**
- Natural cancellation by breaking the loop
- No timer drift - explicit timing control
- Composable with Promise.race, yield*, etc.
- Integrates with all async iterable APIs
- Clear control flow

### Advanced Polling Patterns

**Polling with timeout:**

```typescript
async function* intervalWithTimeout(intervalMs: number, timeoutMs: number) {
  const startTime = Date.now();

  while (Date.now() - startTime < timeoutMs) {
    yield;
    await new Promise((r) => setTimeout(r, intervalMs));
  }
}

for await (const _ of intervalWithTimeout(1000, 10000)) {
  // Polls every 1s, stops after 10s
  await checkCondition();
}
```

---

## Async/Promise Best Practices

### Remove Unnecessary `async`

**Incorrect:**

```typescript
// BAD - Unnecessary async wrapper
async function validateMemory(content: string): boolean {
  if (content.trim().length === 0) {
    throw new Error("Content cannot be empty");
  }
  return true;
}
```

**Correct:**

```typescript
// GOOD - No async needed
function validateMemory(content: string): boolean {
  if (content.trim().length === 0) {
    throw new Error("Content cannot be empty");
  }
  return true;
}
```

### Interface Compliance with Promise.resolve()

When implementing an interface that requires `Promise<T>` but your logic is synchronous:

```typescript
interface QueueMessageHandler {
  handle(message: QueueMessage): Promise<void>;
}

// GOOD - Return Promise.resolve() explicitly
class SyncMessageHandler implements QueueMessageHandler {
  handle(message: QueueMessage): Promise<void> {
    this.processSync(message);
    return Promise.resolve();
  }
}

// GOOD - Return Promise.reject() for errors
class ValidatingHandler implements QueueMessageHandler {
  handle(message: QueueMessage): Promise<void> {
    if (message.corrupted) {
      return Promise.reject(new Error("Message corrupted"));
    }
    return Promise.resolve();
  }
}
```

### Only Use `async` When You Actually `await`

```typescript
// GOOD - async because we await
async function processMemory(content: string): Promise<ProcessedMemory> {
  const embedding = await ollama.generateEmbedding(content);
  const entities = await ollama.extractEntities(content);
  return { content, embedding, entities };
}

// GOOD - no async because no await
function validateConfig(config: Config): boolean {
  return config.apiKey !== undefined;
}
```

---

## Error Handling

### Deno's Class-Based Errors

**Old Pattern (Node.js):**

```javascript
// String-based error codes
try {
  fs.readFileSync('file');
} catch (err) {
  if (err.code === 'ENOENT') {
    // handle not found
  }
}
```

**New Pattern (Deno):**

```typescript
// Type-safe error classes
try {
  await Deno.readTextFile("file.txt");
} catch (err) {
  if (err instanceof Deno.errors.NotFound) {
    // handle not found
  } else if (err instanceof Deno.errors.PermissionDenied) {
    // handle permission error
  }
}
```

### Available Deno Error Classes

```typescript
Deno.errors.NotFound
Deno.errors.PermissionDenied
Deno.errors.ConnectionRefused
Deno.errors.ConnectionReset
Deno.errors.ConnectionAborted
Deno.errors.NotConnected
Deno.errors.AddrInUse
Deno.errors.AddrNotAvailable
Deno.errors.BrokenPipe
Deno.errors.AlreadyExists
Deno.errors.InvalidData
Deno.errors.TimedOut
Deno.errors.Interrupted
Deno.errors.WriteZero
Deno.errors.UnexpectedEof
Deno.errors.BadResource
Deno.errors.Busy
```

### Error Handling Best Practices

```typescript
// Specific error handling
async function loadConfig(path: string): Promise<Config> {
  try {
    const content = await Deno.readTextFile(path);
    return JSON.parse(content);
  } catch (err) {
    if (err instanceof Deno.errors.NotFound) {
      throw new Error(`Config file not found: ${path}`);
    } else if (err instanceof Deno.errors.PermissionDenied) {
      throw new Error(`Permission denied reading config: ${path}`);
    } else if (err instanceof SyntaxError) {
      throw new Error(`Invalid JSON in config: ${path}`);
    }
    throw err;  // Re-throw unknown errors
  }
}
```

**Benefits:**
- Type-safe - no magic string codes
- Better IDE autocomplete
- Easier refactoring
- Clear error hierarchies

---

## Web-Standard APIs

### HTTP Server

**Old (Node.js):**

```javascript
// Node.js style
const http = require("http");
http.createServer((req, res) => {
  res.writeHead(200);
  res.end("OK");
}).listen(8000);
```

**New (Deno):**

```typescript
// Deno - serverless-compatible
Deno.serve((req) => new Response("OK"));
```

**Benefits:**
- Simpler, cleaner API
- Native Request/Response objects
- Works with serverless platforms
- No legacy API constraints

### File Operations

**Old (Node.js):**

```javascript
// Node.js callbacks
fs.readFile('file.txt', 'utf8', (err, data) => {
  if (err) throw err;
  console.log(data);

Related in Backend & APIs