Using this package? Please consider donating to support the proposal ❤️
Help try
grow! Star and share this amazing repository with your friends and co-workers!
A 373-byte spec-compliant runtime-only implementation of the
Result
class from the ECMAScript Try Operator proposal.
import { t } from 'try';
const [ok, error, value] = t(JSON.parse, '{"foo": "bar"}');
const [ok, error, value] = await t(axios.get('https://arthur.place'));
This package is a minimal and precise reference implementation of the Result
class as described in the Try Operator proposal for JavaScript.
It aims to provide a lightweight and fast runtime utility that reflects exactly how the proposed try
operator would behave, and will not evolve independently of the proposal.
If you'd like to suggest changes or improvements to the behavior or API, please open an issue on the proposal repository. Once discussed and approved there, changes will be reflected in this package.
- Why This Exists
- Usage
- Works With Promises Too!
- No
Result.bind
- Creating Results Manually
- Learn More
- Acknowledgements
- License
import { Result, ok, error, t } from 'try';
// Synchronous function call
const [ok1, err1, val1] = t(JSON.parse, '{"foo":"bar"}');
// Arrow function context
const [ok2, err2, val2] = t(() => decoder.decode(buffer));
// Promise call
const [ok3, err3, val3] = await t(fetch, 'https://api.example.com');
// Promise-safe call (safely catches both sync and async errors)
const [ok4, err4, val4] = await t(() => readFile('./config.json'));
// Argument passthrough
const [ok5, err5, val5] = t((a, b) => a + b, 2, 3);
// Keep full result object for readability
const result = await t(fetch, 'https://arthur.place');
if (result.ok) console.log(await result.value.text());
// Manual success and error results
const success = ok(42);
const failure = error(new Error('nope'));
// Manual Result creation via class
const successObj = Result.ok('done');
const failureObj = Result.error('fail');
JavaScript error handling can be verbose and inconsistent. The Try Operator proposal introduces a new pattern that returns structured Result
objects instead of throwing exceptions, simplifying async and sync error flows alike.
While the proposal is still in the works, this package provides a way to experiment with the new pattern in a standardized way.
This package provides a drop-in utility: Result.try()
(or the shorter t()
) to wrap expressions and handle errors in a clean, tuple-like form.
const [ok, error, value] = t(JSON.parse, '{"foo":"bar"}');
if (ok) {
console.log(value.foo);
} else {
console.error('Invalid JSON', error);
}
You can destructure the result into
[ok, error, value]
, or access.ok
,.error
, and.value
directly depending on your use case.
All methods are documented via TSDoc, so your editor will guide you with full type support and autocomplete.
Use Result.try()
or t()
to wrap a potentially failing operation:
const [ok, error, value] = Result.try(() => JSON.parse(request.body));
if (ok) {
console.log(`Hello ${value.name}`);
} else {
console.error(`Invalid JSON!`);
}
Note
Result.try(() => fn())
is verbose compared to the proposal's future try fn()
syntax. Always prefer to use the t()
alias for cleaner code.
To make code cleaner and more ergonomic while we wait for language-level syntax sugar, this package also exports t
, a shortcut for Result.try
.
import { readFile } from 'node:fs/promises';
import { readFileSync } from 'node:fs';
// Example (this is void)
const [ok, error, value] = t(readFileSync, './config.json');
// If `this` matters in your context
const [ok, error, value] = t(() => decoder.decode(request.body));
// Promises don't need wrapping
const [ok, error, value] = await t(axios.get('http://example.com'));
// Safer way even for promises (catches sync errors before returning a promise)
const [ok, error, value] = await t(readFile, path);
The t(fn, ...args)
form is ideal: it automatically passes arguments to your function, preserves full TypeScript inference, and keeps code short and readable.
function divide(a: number, b: number) {
return a / b;
}
const [ok, error, value] = t(divide, 10, 2);
// ok: true, value: 5
While destructuring works well for simple use cases, it can lead to awkward variable naming and clutter when handling multiple try
results. In these cases, it's recommended to keep the full result object and access .ok
, .error
, and .value
directly for better clarity and readability.
❌ Bad (managing variable names becomes cumbersome):
// error handling omitted for brevity
const [ok1, error1, value1] = Result.try(() => axios.get(...));
const [ok2, error2, value2] = Result.try(() => value1.data.property);
✅ Better (clearer structure and easier to follow):
// error handling omitted for brevity
const response = await Result.try(fetch('https://arthur.place'));
const data = await Result.try(() => response.value.text()));
Using the result object directly avoids unnecessary boilerplate and naming inconsistencies, especially in nested or sequential operations. It's a cleaner, more scalable pattern that mirrors real-world error handling flows.
You can pass a Promise
directly and get a Result
-wrapped version:
const [ok, error, value] = await t(
fs.promises.readFile('./config.json')
);
if (ok) {
const config = JSON.parse(value.toString());
} else {
console.error('Failed to read file', error);
}
The return value of t()
is automatically await
-able if the function returns a promise, no extra handling required.
This implementation will never provide a Result.bind()
(like util.promisify
) because the Try Operator follows the Caller’s Approach model.
That means error handling belongs to the calling context, not the function itself. Wrapping a function with bind()
would push error encapsulation into the callee, breaking that principle.
In short: the caller chooses to wrap a function call in Result.try
, not the function author.
You can also create Result
objects directly:
import { Result, ok, error } from 'try';
// With full Result class
const res1 = Result.ok('done');
const res2 = Result.error('fail');
// Shorthand
const okRes = ok(42);
const errRes = error('oops');
This is useful when bridging non-try-based code or mocking results.
To learn about the underlying proposal, including syntax goals and motivation, visit:
🔗 https://github.com/arthurfiorette/proposal-try-operator
Many thanks to Szymon Wygnański for transferring the try
package name on NPM to this project. Versions below 1.0.0
served a different purpose, but with his permission, the project was repurposed to host an implementation of the proposal’s Result
class.
Both the project and the proposal are licensed under the MIT license.