Skip to content

A 373-byte Spec-Compliant Runtime-Only Implementation of the ECMAScript Try Operator Proposal Result class

License

Notifications You must be signed in to change notification settings

arthurfiorette/try

Repository files navigation

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!

License Downloads Bundlephobia Last commit Codecov


TRY

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.



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');

Why This Exists

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.


Usage

All methods are documented via TSDoc, so your editor will guide you with full type support and autocomplete.

Wrapping a Function Call

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.


t() alias

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

Prefer Using the Result Object in Multi-Try Scenarios

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.


Works With Promises Too!

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.


No Result.bind

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.


Creating Results Manually

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.


Learn More

To learn about the underlying proposal, including syntax goals and motivation, visit:

🔗 https://github.com/arthurfiorette/proposal-try-operator


Acknowledgements

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.


License

Both the project and the proposal are licensed under the MIT license.


About

A 373-byte Spec-Compliant Runtime-Only Implementation of the ECMAScript Try Operator Proposal Result class

Topics

Resources

License

Code of conduct

Security policy

Stars

Watchers

Forks

Sponsor this project

 

Contributors 2

  •  
  •