Skip to main content

Quick Start

This guide will get you up and running with go-go-scope in minutes. You’ll learn the core patterns for structured concurrency and automatic resource cleanup.

Prerequisites

  • Node.js 24+ or Bun 1.2+
  • TypeScript 5.2+
  • Basic understanding of async/await
If you haven’t installed go-go-scope yet, see the Installation guide.

Your First Scope

The scope() function creates a structured concurrency boundary with automatic cleanup:
import { scope } from "go-go-scope";

// Create a scope with automatic cleanup
await using s = scope();

const [err, result] = await s.task(async () => {
  return "Hello, structured concurrency!";
});

if (err) {
  console.error("Task failed:", err);
} else {
  console.log("Success:", result);
}

// Scope automatically cleaned up here
The await using syntax ensures the scope is disposed when it goes out of block scope, even if an error occurs.

Result Tuples

go-go-scope uses Result tuples instead of try/catch for error handling:
const [error, value] = await s.task(() => fetchData());

if (error) {
  // Handle error case
  return;
}

// TypeScript knows value is defined here
console.log(value.data);
This pattern:
  • Eliminates forgotten error handling
  • Makes error cases explicit
  • Provides better type safety

Running Tasks in Parallel

Execute multiple tasks concurrently with automatic result collection:
await using s = scope();

const results = await s.parallel([
  () => fetchUser(123),
  () => fetchPosts(123),
  () => fetchComments(123),
]);

// Results are in the same order as input
const [userErr, user] = results[0];
const [postsErr, posts] = results[1];
const [commentsErr, comments] = results[2];

Concurrency Limits

Control how many tasks run simultaneously:
const userIds = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];

const results = await s.parallel(
  userIds.map((id) => () => fetchUser(id)),
  { concurrency: 3 } // Only 3 requests at a time
);

Timeouts

Set deadlines for operations:
await using s = scope({ timeout: 5000 }); // 5 second timeout

const [err, data] = await s.task(() => slowOperation());

if (err) {
  console.error("Operation timed out or failed");
}
Per-task timeouts:
const [err, result] = await s.task(
  () => fetchData(),
  { timeout: 2000 } // This task has 2 second timeout
);

Automatic Cancellation

When a scope is disposed, all running tasks are cancelled:
{
  await using s = scope();
  
  s.task(async ({ signal }) => {
    // Long-running operation
    const response = await fetch("https://api.example.com/data", { signal });
    return response.json();
  });
  
  // Scope disposed here - fetch is automatically cancelled
}
Always pass the signal to operations that support cancellation (fetch, database queries, etc.).

Complete Example: API Request Pipeline

Here’s a realistic example combining multiple features:
1

Create scope with timeout

Set an overall deadline for the operation:
await using s = scope({ timeout: 10000 });
2

Fetch user data

Get the user with error handling:
const [userErr, user] = await s.task(
  async ({ signal }) => {
    const response = await fetch(
      `https://api.example.com/users/${userId}`,
      { signal }
    );
    return response.json();
  },
  { timeout: 3000 } // Per-task timeout
);

if (userErr) {
  console.error("Failed to fetch user:", userErr);
  return;
}
3

Fetch related data in parallel

Get posts and comments concurrently:
const results = await s.parallel([
  async ({ signal }) => {
    const res = await fetch(
      `https://api.example.com/posts?userId=${user.id}`,
      { signal }
    );
    return res.json();
  },
  async ({ signal }) => {
    const res = await fetch(
      `https://api.example.com/comments?userId=${user.id}`,
      { signal }
    );
    return res.json();
  },
]);

const [postsErr, posts] = results[0];
const [commentsErr, comments] = results[1];
4

Process results

Combine the data:
return {
  user,
  posts: postsErr ? [] : posts,
  comments: commentsErr ? [] : comments,
};

Full Code

import { scope } from "go-go-scope";

async function getUserDashboard(userId: number) {
  await using s = scope({ timeout: 10000 });

  // Fetch user
  const [userErr, user] = await s.task(
    async ({ signal }) => {
      const response = await fetch(
        `https://api.example.com/users/${userId}`,
        { signal }
      );
      return response.json();
    },
    { timeout: 3000 }
  );

  if (userErr) {
    console.error("Failed to fetch user:", userErr);
    return null;
  }

  // Fetch posts and comments in parallel
  const results = await s.parallel([
    async ({ signal }) => {
      const res = await fetch(
        `https://api.example.com/posts?userId=${user.id}`,
        { signal }
      );
      return res.json();
    },
    async ({ signal }) => {
      const res = await fetch(
        `https://api.example.com/comments?userId=${user.id}`,
        { signal }
      );
      return res.json();
    },
  ]);

  const [postsErr, posts] = results[0];
  const [commentsErr, comments] = results[1];

  return {
    user,
    posts: postsErr ? [] : posts,
    comments: commentsErr ? [] : comments,
  };
}

// Usage
const dashboard = await getUserDashboard(123);
console.log(dashboard);

Go-Style Channels

Communicate between concurrent tasks using channels:
await using s = scope();

// Create a channel with buffer capacity
const ch = s.channel<number>({ capacity: 10 });

// Producer task
s.task(async () => {
  for (let i = 0; i < 100; i++) {
    await ch.send(i);
  }
  ch.close();
});

// Consumer task
s.task(async () => {
  for await (const value of ch) {
    console.log("Received:", value);
  }
});

Retry with Backoff

Automatically retry failed operations:
const [err, data] = await s.task(
  () => unreliableApiCall(),
  {
    retry: {
      maxAttempts: 3,
      delay: (attempt) => Math.pow(2, attempt) * 1000, // Exponential backoff
    },
  }
);
Or use built-in strategies:
import { exponentialBackoff } from "go-go-scope";

const [err, data] = await s.task(
  () => unreliableApiCall(),
  {
    retry: {
      maxAttempts: 5,
      delay: exponentialBackoff({ baseDelay: 1000, maxDelay: 30000 }),
    },
  }
);

Circuit Breaker

Prevent cascading failures with circuit breaker pattern:
import { scope, CircuitBreaker } from "go-go-scope";

const breaker = new CircuitBreaker({
  failureThreshold: 5,
  resetTimeout: 60000, // 1 minute
});

await using s = scope({ circuitBreaker: breaker });

const [err, data] = await s.task(() => externalServiceCall());

if (err) {
  // Circuit breaker may be open if too many failures
  console.error("Call failed:", err.message);
}

Next Steps

You now know the basics of structured concurrency with go-go-scope!

Core Concepts

Deep dive into structured concurrency principles

Channels

Learn about Go-style communication patterns

Resilience

Circuit breakers, retries, and fault tolerance

Framework Integration

Use go-go-scope with your favorite framework

Build docs developers (and LLMs) love