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:
Create scope with timeout
Set an overall deadline for the operation: await using s = scope ({ timeout: 10000 });
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 ;
}
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 ];
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