go-go-try
v7.1.0
Published
Tries to execute a sync/async function, returns a result tuple
Maintainers
Readme
go-go-try
Tries to execute a sync/async function, returns a Golang style result.
Why
- Supports sync/async functions.
- Allows you to capture the thrown error.
- Written in TypeScript. The types are written in a way that reduce developer errors.
- Inspired by Golang error catching.
- Zero dependencies.
Why not just try/catch?
- In a lot of cases,
try/catchis still the better option. - Nested
try/catchstatements are hard to process mentally. They also indent the code and make it hard to read. A singletry/catchdoes the same but to a lesser degree. - If you prefer const,
try/catchstatements get in the way because you need to useletif you need the variable outside of thetry/catchscope:let todos try { todos = JSON.parse(localStorage.getItem('todos')) } catch {} return todos.filter((todo) => todo.done) - It takes more space. It's slower to type.
Install
npm install go-go-tryBasic Usage
import { goTry, goTryRaw } from 'go-go-try'
// tries to parse todos, returns empty array if it fails
const [_, value = []] = goTry(() => JSON.parse(todos))
// fetch todos, on error, fallback to empty array
const [_, todos = []] = await goTry(fetchTodos())
// fetch todos, fallback to empty array, send error to your error tracking service
const [err, todos = []] = await goTry(fetchTodos()) // err is string | undefined
if (err) sendToErrorTrackingService(err)
// goTry extracts the error message from the error object, if you want the raw error object, use goTryRaw
const [err, value] = goTryRaw(() => JSON.parse('{/}')) // err is Error | undefined, value is T | undefined
// fetch todos, fallback to empty array, send error to your error tracking service
const [err, todos = []] = await goTryRaw(fetchTodos()) // err is Error | undefined
if (err) sendToErrorTrackingService(err)Advanced Usage
Sequential Async Operations
Chain multiple async operations with clean error handling:
import { goTry } from 'go-go-try'
async function fetchUserData(userId: string) {
// Fetch user
const [fetchErr, user] = await goTry(fetch(`/api/users/${userId}`))
if (fetchErr) return [fetchErr, undefined] as const
// Parse response
const [parseErr, data] = await goTry(user!.json())
if (parseErr) return [parseErr, undefined] as const
// Validate/transform
const [validateErr, validated] = goTry(() => validateUser(data!))
if (validateErr) return [validateErr, undefined] as const
return [undefined, validated] as const
}
const [err, user] = await fetchUserData('123')
if (err) {
console.error('Failed to fetch user:', err)
} else {
console.log('User:', user)
}Parallel Execution with goTryAll
Execute multiple promises in parallel:
import { goTryAll } from 'go-go-try'
const [errors, results] = await goTryAll([
fetchUser(userId),
fetchPosts(userId),
fetchComments(userId)
])
// errors is [string | undefined, string | undefined, string | undefined]
// results is [User | undefined, Posts | undefined, Comments | undefined]
const [user, posts, comments] = resultsSafe Unwrapping with goTryOr
Like goTry, but returns a default value on failure instead of undefined:
Note: For static default values, you can use destructuring instead:
// These are equivalent for static defaults: const [err, config = {port: 3000}] = goTry(() => JSON.parse(configString)) const [err, config] = goTryOr(() => JSON.parse(configString), {port: 3000})Use
goTryOrwhen you need lazy evaluation (the default is only computed on failure):
import { goTryOr } from 'go-go-try'
// ✅ Use goTryOr with a function for lazy evaluation - default only computed on failure
const [err, user] = await goTryOr(fetchUser(id), () => ({
id: 'anonymous',
name: 'Guest',
createdAt: new Date() // This won't run on success
}))
// ❌ Avoid: wasteful - createDefault() runs even on success
const [err, config = createDefault()] = goTry(loadConfig())
// ✅ Better: lazy - createDefault() only runs on failure
const [err, config] = goTryOr(loadConfig(), () => createDefault())Express/Fastify Error Handling
Use in API route handlers:
import { goTry } from 'go-go-try'
import express from 'express'
const app = express()
app.post('/users', async (req, res) => {
const [err, user] = await goTry(createUser(req.body))
if (err) {
return res.status(400).json({ error: err })
}
res.json(user)
})
// Batch endpoint
app.post('/batch', async (req, res) => {
const [errors, results] = await goTryAll(
req.body.operations.map(op => processOperation(op))
)
const hasErrors = errors.some(e => e !== undefined)
res.status(hasErrors ? 207 : 200).json({
results,
errors: errors.filter(Boolean)
})
})Type Guards
Narrow types using isSuccess and isFailure:
import { goTry, isSuccess, isFailure } from 'go-go-try'
const result = goTry(() => riskyOperation())
if (isSuccess(result)) {
// result[1] is typed as T (not T | undefined)
console.log(result[1])
} else if (isFailure(result)) {
// result[0] is typed as E (not E | undefined)
console.error(result[0])
}You can also narrow types by destructuring and checking the error:
const [err, value] = goTry(() => riskyOperation())
if (err === undefined) {
// value is typed as T (not T | undefined)
console.log(value)
} else {
// err is typed as string (not string | undefined)
console.error(err)
// value is typed as undefined in this branch
}Helper Functions
Build custom utilities on top of the primitives:
import { goTry, success, failure, type Result } from 'go-go-try'
// Custom validation helper
function validateEmail(email: string): Result<string, string> {
if (!email.includes('@')) {
return failure('Invalid email format')
}
return success(email.toLowerCase().trim())
}
// Usage
const [err, normalizedEmail] = validateEmail('[email protected]')
if (err) {
console.error(err) // Doesn't trigger
} else {
console.log(normalizedEmail) // '[email protected]'
}API
goTry<T>(value)
Executes a function, promise, or value and returns a Result type with error message as string.
function goTry<T>(value: T | Promise<T> | (() => T | Promise<T>)): Result<string, T> | Promise<Result<string, T>>goTryRaw<T, E>(value)
Like goTry but returns the raw Error object instead of just the message.
function goTryRaw<T, E = Error>(value: T | Promise<T> | (() => T | Promise<T>)): Result<E, T> | Promise<Result<E, T>>goTryAll<T>(items, options?)
Executes multiple promises or factory functions with optional concurrency limit. Returns a tuple of [errors, results] with fixed tuple types preserving input order.
interface GoTryAllOptions {
concurrency?: number // 0 = unlimited (default), 1 = sequential, N = max concurrent
}
function goTryAll<T extends readonly unknown[]>(
items: { [K in keyof T]: Promise<T[K]> | (() => Promise<T[K]>) },
options?: GoTryAllOptions
): Promise<[{ [K in keyof T]: string | undefined }, { [K in keyof T]: T[K] | undefined }]>Promise mode (pass promises directly):
// Run all in parallel (default):
const [errors, results] = await goTryAll([
fetchUser(1), // Promise<User>
fetchUser(2), // Promise<User>
fetchUser(3), // Promise<User>
])
// errors: [string | undefined, string | undefined, string | undefined]
// results: [User | undefined, User | undefined, User | undefined]Factory mode (pass functions that return promises):
// True lazy execution - factories only called when a slot is available
const [errors, results] = await goTryAll([
() => fetchUser(1), // Only called when concurrency slot available
() => fetchUser(2), // Only called when concurrency slot available
() => fetchUser(3), // Only called when concurrency slot available
() => fetchUser(4), // Only called when concurrency slot available
], { concurrency: 2 })
// Use factory mode when you need to:
// - Rate limit API calls (don't start HTTP requests until allowed)
// - Control database connection limits
// - Limit expensive computation resourcesgoTryAllRaw<T>(items, options?)
Like goTryAll, but returns raw Error objects instead of error messages.
function goTryAllRaw<T extends readonly unknown[]>(
items: { [K in keyof T]: Promise<T[K]> | (() => Promise<T[K]>) },
options?: GoTryAllOptions
): Promise<[{ [K in keyof T]: Error | undefined }, { [K in keyof T]: T[K] | undefined }]>goTryOr<T>(value, defaultValue)
Like goTry, but returns a default value on failure instead of undefined.
The default can be either a static value or a function (for lazy evaluation).
function goTryOr<T>(value: T | Promise<T> | (() => T | Promise<T>), defaultValue: T | (() => T)): Result<string, T> | Promise<Result<string, T>>isSuccess(result) / isFailure(result)
Type guards to check result status.
function isSuccess<E, T>(result: Result<E, T>): result is Success<T>
function isFailure<E, T>(result: Result<E, T>): result is Failure<E>success(value) / failure(error)
Helper functions to create Result tuples.
function success<T>(value: T): Success<T>
function failure<E>(error: E): Failure<E>Types
type Success<T> = readonly [undefined, T]
type Failure<E> = readonly [E, undefined]
type Result<E, T> = Success<T> | Failure<E>License
MIT
