try
Version:
A 373-byte Spec-Compliant Runtime-Only Implementation of the ECMAScript Try Operator Proposal Result class
257 lines (172 loc) • 9.3 kB
Markdown
<p align="center">
<b>Using this package?</b> Please consider <a href="https://github.com/arthurfiorette/try?sponsor=1" target="_blank">donating</a> to support the proposal ❤️
<br />
<sup>
Help <code>try</code> grow! Star and share this amazing repository with your friends and co-workers!
</sup>
</p>
<p align="center">
<a title="MIT license" target="_blank" href="https://github.com/arthurfiorette/try/blob/main/LICENSE"><img alt="License" src="https://img.shields.io/github/license/arthurfiorette/try"></a>
<a title="NPM Package" target="_blank" href="https://www.npmjs.com/package/try"><img alt="Downloads" src="https://img.shields.io/npm/dw/try?style=flat"></a>
<a title="Bundle size" target="_blank" href="https://bundlephobia.com/package/try"><img alt="Bundlephobia" src="https://img.shields.io/bundlephobia/minzip/try/latest?style=flat"></a>
<a title="Last Commit" target="_blank" href="https://github.com/arthurfiorette/try/commits/main"><img alt="Last commit" src="https://img.shields.io/github/last-commit/arthurfiorette/try"></a>
<a title="Codecov" target="_blank" href="https://app.codecov.io/gh/arthurfiorette/try"><img alt="Codecov" src="https://codecov.io/gh/arthurfiorette/try/graph/badge.svg?token=ky185JbytA"></a>
</p>
<br />
<h1>TRY</h1>
> A [373-byte](https://bundlephobia.com/package/try) spec-compliant runtime-only implementation of the [`Result` class from the ECMAScript Try Operator proposal](https://github.com/arthurfiorette/proposal-try-operator).
```ts
import { t } from 'try';
const [ok, error, value] = t(() => JSON.parse('{"foo": "bar"}'));
const [ok, error, value] = await t(axios.get('https://arthur.place'));
```
<br />
This package is a minimal and precise reference implementation of the [`Result` class](https://github.com/arthurfiorette/proposal-try-operator#result-class) as described in the [Try Operator](https://github.com/arthurfiorette/proposal-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](https://github.com/arthurfiorette/proposal-try-operator/issues/new/choose). Once discussed and approved there, changes will be reflected in this package.
<br />
- [Why This Exists](#why-this-exists)
- [Usage](#usage)
- [Wrapping a Function Call](#wrapping-a-function-call)
- [`t()` alias](#t-alias)
- [Prefer Using the Result Object in Multi-Try Scenarios](#prefer-using-the-result-object-in-multi-try-scenarios)
- [Works With Promises Too!](#works-with-promises-too)
- [No `Result.bind`](#no-resultbind)
- [Creating Results Manually](#creating-results-manually)
- [Learn More](#learn-more)
- [Acknowledgements](#acknowledgements)
- [License](#license)
<br />
```ts
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');
```
<br />
## Why This Exists
JavaScript error handling can be verbose and inconsistent. The [Try Operator proposal](https://github.com/arthurfiorette/proposal-try-operator) 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.
```ts
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.
<br />
## 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:
```ts
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.
<br />
### `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`.
```ts
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.
```ts
function divide(a: number, b: number) {
return a / b;
}
const [ok, error, value] = t(divide, 10, 2);
// ok: true, value: 5
```
<br />
### 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):
```ts
// 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):
```ts
// 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.
<br />
## Works With Promises Too!
You can pass a `Promise` directly and get a `Result`-wrapped version:
```ts
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.
<br />
## No `Result.bind`
This implementation **will never provide a `Result.bind()`** (like `util.promisify`) because the Try Operator follows the [Caller’s Approach](https://github.com/arthurfiorette/proposal-try-operator/tree/main#callers-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.
<br />
## Creating Results Manually
You can also create `Result` objects directly:
```ts
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.
<br />
## Learn More
To learn about the underlying proposal, including syntax goals and motivation, visit:
🔗 https://github.com/arthurfiorette/proposal-try-operator
<br />
## Acknowledgements
Many thanks to [Szymon Wygnański](https://finalclass.net) 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.
<br />
## License
Both the project and the proposal are licensed under the [MIT](./LICENSE) license.
<br />