Tries to execute a sync/async function, returns a result tuple
npm install go-go-trygo-go-try> Tries to execute a sync/async function, returns a Golang style result.
- 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/catch is still the better option.
- Nested try/catch statements are hard to process mentally. They also indent the code and make it hard to read. A single try/catch does the same but to a lesser degree.
- If you prefer const, try/catch statements get in the way because you need to use let if you need the variable outside of the try/catch scope:
``ts`
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.
`bash`
npm install go-go-try
`ts
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)
`
Chain multiple async operations with clean error handling:
`ts
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)
}
`
Execute multiple promises in parallel:
`ts
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] = results
`
Like goTry, but returns a default value on failure instead of undefined:
> Note: For static default values, you can use destructuring instead:
> `ts`
> // These are equivalent for static defaults:
> const [err, config = {port: 3000}] = goTry(() => JSON.parse(configString))
> const [err, config] = goTryOr(() => JSON.parse(configString), {port: 3000})
> goTryOr
> Use when you need lazy evaluation (the default is only computed on failure):
`ts
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())
`
Use in API route handlers:
`ts
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)
})
})
`
Narrow types using isSuccess and isFailure:
`ts
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:
`ts
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
}
`
Build custom utilities on top of the primitives:
`ts
import { goTry, success, failure, type Result } from 'go-go-try'
// Custom validation helper
function validateEmail(email: string): Result
if (!email.includes('@')) {
return failure('Invalid email format')
}
return success(email.toLowerCase().trim())
}
// Usage
const [err, normalizedEmail] = validateEmail('User@Example.COM')
if (err) {
console.error(err) // Doesn't trigger
} else {
console.log(normalizedEmail) // 'user@example.com'
}
`
Executes a function, promise, or value and returns a Result type with error message as string.
`ts`
function goTry
Like goTry but returns the raw Error object instead of just the message.
`ts`
function goTryRaw
Executes multiple promises or factory functions with optional concurrency limit. Returns a tuple of [errors, results] with fixed tuple types preserving input order.
`ts
interface GoTryAllOptions {
concurrency?: number // 0 = unlimited (default), 1 = sequential, N = max concurrent
}
function goTryAll
items: { [K in keyof T]: Promise
options?: GoTryAllOptions
): Promise<[{ [K in keyof T]: string | undefined }, { [K in keyof T]: T[K] | undefined }]>
`
Promise mode (pass promises directly):
`ts`
// Run all in parallel (default):
const [errors, results] = await goTryAll([
fetchUser(1), // Promise
fetchUser(2), // Promise
fetchUser(3), // Promise
])
// errors: [string | undefined, string | undefined, string | undefined]
// results: [User | undefined, User | undefined, User | undefined]
Factory mode (pass functions that return promises):
`ts
// 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 resources
`
Like goTryAll, but returns raw Error objects instead of error messages.
`ts`
function goTryAllRaw
items: { [K in keyof T]: Promise
options?: GoTryAllOptions
): Promise<[{ [K in keyof T]: Error | undefined }, { [K in keyof T]: T[K] | undefined }]>
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).
`ts`
function goTryOr
Type guards to check result status.
`ts`
function isSuccess
function isFailure
Helper functions to create Result tuples.
`ts`
function success
function failure
`ts``
type Success
type Failure
type Result
MIT