{"id":25013813,"url":"https://github.com/realpha/eitherway","last_synced_at":"2025-04-12T22:06:33.225Z","repository":{"id":206877165,"uuid":"681156465","full_name":"realpha/eitherway","owner":"realpha","description":"Yet Another Option and Result Implementation - providing safe abstractions for fallible flows inspired by F# and Rust","archived":false,"fork":false,"pushed_at":"2024-04-03T15:37:31.000Z","size":247,"stargazers_count":10,"open_issues_count":9,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-03-26T16:11:32.440Z","etag":null,"topics":["either","fsharp","maybe","option","result","rust","task","typescript"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/realpha.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE.md","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":".github/CODEOWNERS","security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2023-08-21T11:48:19.000Z","updated_at":"2024-03-26T11:32:08.000Z","dependencies_parsed_at":"2024-11-20T08:58:51.528Z","dependency_job_id":"98106ecb-2df5-43fa-8d56-362beeaa26ed","html_url":"https://github.com/realpha/eitherway","commit_stats":{"total_commits":99,"total_committers":1,"mean_commits":99.0,"dds":0.0,"last_synced_commit":"0b77150ddad98fb03bfcc18421c32f5c89c137b8"},"previous_names":["realpha/eitherway"],"tags_count":19,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/realpha%2Feitherway","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/realpha%2Feitherway/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/realpha%2Feitherway/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/realpha%2Feitherway/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/realpha","download_url":"https://codeload.github.com/realpha/eitherway/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248008631,"owners_count":21032556,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["either","fsharp","maybe","option","result","rust","task","typescript"],"created_at":"2025-02-05T07:15:55.198Z","updated_at":"2025-04-12T22:06:33.169Z","avatar_url":"https://github.com/realpha.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# eitherway\n\n[![maintainability](https://api.codeclimate.com/v1/badges/dc2d6e0d46d4b6b304f6/maintainability)](https://codeclimate.com/github/realpha/eitherway/maintainability)\n![ci](https://github.com/realpha/eitherway/actions/workflows/ci.yml/badge.svg)\n[![coverage](https://api.codeclimate.com/v1/badges/dc2d6e0d46d4b6b304f6/test_coverage)](https://codeclimate.com/github/realpha/eitherway/test_coverage)\n[![deno](https://shield.deno.dev/x/eitherway)](https://deno.land/x/eitherway)\n[![npm](https://img.shields.io/npm/v/eitherway)](https://www.npmjs.com/package/eitherway)\n![release](https://github.com/realpha/eitherway/actions/workflows/release.yml/badge.svg)\n\n\u003e Yet Another Option and Result Implementation (**YAORI**)\n\nSafe abstractions for fallible flows inspired by F# and Rust.\n\n## Disclaimer\n\n🚧 This project is still under development, expect some breaking changes 🚧\n\nPlease see this [tracking issue](https://github.com/realpha/eitherway/issues/9)\nfor more information regarding when to expect a stable release.\n\n## Contents\n\n- [Motivation](#motivation)\n- [Design Goals](#design-goals)\n- [`eitherway` in Action](#eitherway-in-action)\n  - [Installation](#installation)\n- [API](#api)\n  - [Overview](#overview)\n    - [Design Decisions](#design-decisions)\n    - [Additions](#additions)\n  - [Option](#option)\n  - [Result](#result)\n  - [Task](#task)\n- [Best Practices](#best-practices)\n- [FAQ](#faq)\n- [Prior Art](#prior-art)\n- [License and Contributing](#license-and-contributing)\n\n## Motivation\n\nLet's be honest: The community already nurtured a bunch of great projects,\nproviding similar abstractions. The goal has always been to make it easier and\nmore explicit to handle error cases under the\n[\"errors are values\" premise](https://www.youtube.com/watch?v=PAAkCSZUG1c\u0026t=16m13s).\nAfter having worked with quite a few of these existing abstractions, a couple of\nissues arose/persisted:\n\n- **Overly strict**: Some of the existing implementations don't account for the\n  variance emerging naturally in a structural type system.\n- **Onboarding is hard**: Most of the existing projects provide either very\n  little or scattered documentation. Forcing new users to constantly switch\n  context in order to understand how they can achieve their goal or if their\n  chosen operation is suitable for their use case.\n- **Lack of async support**: Very few existing projects offer abstractions for\n  working in an `async` context, none of them really being first class citizens.\n\nThe goal here really is to make the abstractions provided by `eitherway` the\nmost safe, productive and overall enjoyable to work with. Irrespective of\nexperience or employed context.\n\n## Design Goals\n\n`eitherway` is trying to close the gap between type-safety, idiom and\nproductivity by focusing on the following goals:\n\n- **Pragmatism**: There is no need to strictly port something from another\n  language with very different semantics. When decisions arrive, which call for\n  a trade-off, `eitherway` will always try to offer a solution geared towards\n  the constraints and idiom of Typescript.\n- **Compatibility**: Interacting with one of the structures defined by\n  `eitherway` should be painless in the sense that things do what you would\n  expect them to do in Typescript and are compatible with inherent language\n  protocols (e.g. Iterator protocol).\n- **Safety**: Once an abstraction is instantiated, no inherent operation should\n  ever panic.\n- **Performance**: All abstractions provided here should strive to amortize the\n  cost of usage and be upfront about these costs.\n- **Documentation**: All structures come with full, inline documentation. Making\n  it easy to understand what is currently happening and if the chosen operation\n  is suitable for the desired use case. (Still very much in progress)\n\n## `eitherway` in Action\n\n```typescript\nimport { Option, Result, Task } from \"https://deno.land/x/eitherway/mod.ts\";\n\nconst sleep = (ms: number) =\u003e new Promise((resolve) =\u003e setTimeout(resolve, ms));\n\n/**\n * A little API over-use to show what's possible.\n * This is not the most efficient way to write code, but still performs well\n * enough in benchmarks.\n */\n\nfunction toUpperCase(input: string | undefined): Task\u003cstring, TypeError\u003e {\n  return Option(input) // All nullish values are None\n    .okOrElse(() =\u003e TypeError(\"Input is undefined\")) // Convert to Result\u003cstring, TypeError\u003e\n    .into((res) =\u003e Task.of(res)) // Push the result into an async context\n    .map(async (str) =\u003e {\n      await sleep(1);\n      return str.toUpperCase();\n    });\n}\n\nfunction stringToLength(input: string): Task\u003cnumber, TypeError\u003e {\n  return Option.fromCoercible(input) // All falsy types are None\n    .okOrElse(() =\u003e TypeError(\"Input string is empty\"))\n    .into((res) =\u003e Task.of(res))\n    .map(async (str) =\u003e {\n      await sleep(1);\n      return str.length;\n    });\n}\n\nfunction powerOfSelf(input: number): Task\u003cnumber, TypeError\u003e {\n  return Option.fromCoercible(input)\n    .okOrElse(() =\u003e\n      TypeError(\"Cannot perform computation with NaN, Infinity or 0\")\n    )\n    .into((res) =\u003e Task.of(res))\n    .andThen(async (n) =\u003e { // Promise\u003cResult\u003cT, E\u003e\u003e and Task\u003cT, E\u003e can be used interchangeably for async composition\n      await sleep(1);\n      return Option.fromCoercible(Math.pow(n, n))\n        .okOrElse(() =\u003e TypeError(\"Cannot calculate result\"));\n    });\n}\n\nfunction processString(input: string | undefined): Task\u003cnumber, TypeError\u003e {\n  return toUpperCase(input) // Synchronous and asynchronous composition work the same\n    .andThen(stringToLength)\n    .andThen(powerOfSelf);\n}\n\nasync function main(): Promise\u003cResult\u003cnumber, TypeError\u003e\u003e {\n  const result = await processString(\"foo\"); // Task is of course awaitable\n\n  return Task.of(result); // You can return Task\u003cT, E\u003e as Promise\u003cResult\u003cT, E\u003e\u003e\n}\n\nmain()\n  .then((result) =\u003e {\n    result // Result\u003cnumber, TypeError\u003e\n      .inspect(console.log)\n      .inspectErr(console.error);\n  })\n  .catch((e) =\u003e \"Unreachable!\")\n  .finally(() =\u003e console.log(\"DONE!\"));\n```\n\n### Installation\n\n#### Minimum Required Runtime Versions\n\n`eitherway` internally uses the\n[`Error.cause`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error/cause#browser_compatibility)\nconfiguration option and\n[`structuredClone`](https://developer.mozilla.org/en-US/docs/Web/API/structuredClone),\ntherefore please make sure these versions are met:\n\n- `deno`: \u003e=1.14.\n- `node`: \u003e=17.0.0\n- `Browser`: [`Error.cause`](https://caniuse.com/?search=Error.cause) \u0026\n  [`structuredClone`](https://caniuse.com/?search=structuredClone)\n\n| \u003cimg width=\"30px\" height=\"30px\" alt=\"Deno\" src=\"https://res.cloudinary.com/dz3vsv9pg/image/upload/v1620998361/logos/deno.svg\"\u003e\u003c/br\u003edeno | \u003cimg width=\"24px\" height=\"24px\" alt=\"Node.js\" src=\"https://res.cloudinary.com/dz3vsv9pg/image/upload/v1620998361/logos/nodejs.svg\"\u003e\u003c/br\u003enode | \u003cimg width=\"24px\" height=\"24px\" alt=\"IE / Edge\" src=\"https://raw.githubusercontent.com/alrra/browser-logos/master/src/edge/edge_48x48.png\"\u003e\u003c/br\u003eEdge | \u003cimg src=\"https://raw.githubusercontent.com/alrra/browser-logos/master/src/chrome/chrome_48x48.png\" alt=\"Chrome\" width=\"24px\" height=\"24px\" /\u003e\u003c/br\u003eChrome | \u003cimg src=\"https://raw.githubusercontent.com/alrra/browser-logos/master/src/firefox/firefox_48x48.png\" alt=\"Firefox\" width=\"24px\" height=\"24px\" /\u003e\u003c/br\u003eFirefox | \u003cimg src=\"https://raw.githubusercontent.com/alrra/browser-logos/master/src/safari/safari_48x48.png\" alt=\"Safari\" width=\"24px\" height=\"24px\" /\u003e\u003c/br\u003eSafari |\n| --------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| `\u003e=1.14.0`                                                                                                                              | `\u003e=17.0.0`                                                                                                                                   | `\u003e=119`                                                                                                                                              | `\u003e=119`                                                                                                                                                   | `\u003e=119`                                                                                                                                                       | `\u003e=17.1`                                                                                                                                                  |\n\n#### `deno`\n\n```typescript\nimport {\n  Err,\n  None,\n  Ok,\n  Option,\n  Result,\n  Some,\n  Task,\n} from \"https://deno.land/x/eitherway/mod.ts\";\n```\n\n#### `node`\n\n```bash\n(npm | pnpm | yarn) add eitherway\n```\n\n`esm`:\n\n```typescript\nimport { Err, None, Ok, Option, Result, Some, Task } from \"npm:eitherway\";\n```\n\n`cjs`:\n\n```javascript\nconst { Err, Ok, Option, None, Result, Some, Task } = require(\"eitherway\");\n```\n\n## API\n\n### Overview\n\nOn a high level, `eitherway` provides 3 basic abstractions, which have different\nuse cases:\n\n- [`Option\u003cT\u003e`](#option): Composable equivalent of the union `T | undefined`.\n  Use this to handle the case of non-existence gracefully or assert a certain\n  fact about a value.\n- [`Result\u003cT, E\u003e`](#result): Composable equivalent of the union `T | E`. Use\n  this to gracefully handle an happy-path and error-path without the need to\n  throw exceptions.\n- [`Task\u003cT, E\u003e`](#task): Composable equivalent of `Promise\u003cT | E\u003e`. Same as\n  `Result` but for asynchronous operations.\n\n#### Design Decisions\n\nIf you are coming from other languages, or other libraries, you will be familiar\nwith most parts already. A couple of things are handled differently though:\n\n- **Thinking in unions**: Union types are ubiquitous and a powerful feature of\n  Typescript. The abstractions provided by `eitherway` were modeled to provide a\n  tag to members of unions commonly used. As a consequence, there are no `safe`\n  or `unchecked` variants for methods like `.unwrap()`.\n\n```typescript\nimport { Ok, Option, Result } from \"https://deno.land/x/eitherway/mod.ts\";\n\nconst opt: Option\u003cstring\u003e = Option(\"foo\");\nconst res: Result\u003cnumber, TypeError\u003e = Ok(1);\n\n// Without narrowing, the union type is returned\nconst maybeString: string | undefined = opt.unwrap();\nconst numOrError: number | TypeError = res.unwrap();\n\n// The type can easily be narrowed though\nif (res.isErr()) {\n  console.error(res.unwrap());\n}\n\nconst num: number = res.unwrap();\n```\n\n- **Upholding basic invariants**: You CANNOT construct an instance of\n  `Option\u003cundefined | null\u003e` and you MUST NOT throw exceptions when returning\n  `Result\u003cT, E\u003e` or `Task\u003cT, E\u003e` from a function.\n- **Don't panic**: Following the previous statements, `eitherway` does not throw\n  or re-throw exceptions under normal operations. In fact, there are only 3\n  scenarios, which lead to a panic at runtime:\n  1. Trying to shove a nullish value into `Some`. The compiler will not allow\n     this, but if you perform a couple of type casts, or a library you depend on\n     provides wrong type declarations, the `Some` constructor will throw an\n     exception, when you end up trying to instantiate it with a nullish value.\n  2. Trying to lift a `Promise` or a function, which you've explicitly provided\n     as infallible, into a `Result` or `Task` context and it ends up panicking.\n  3. You, despite being told multiple times not to do so, chose to panic in a\n     function you've implicitly marked as infallible by returning a\n     `Result\u003cT, E\u003e`, a `Promise\u003cResult\u003cT, E\u003e\u003e` or a `Task\u003cT, E\u003e`.\n- **Closure of operations**: All mapping and chaining operations are closed,\n  meaning that they return an instance of the same abstraction as the one they\n  were called on.\n\n#### Additions\n\nSome notable additions, which you may have been missing in other libraries:\n\n- **Composable side-effects**: `.tap()`, `.inspect()` and `.inspectErr()`\n  methods.\n- **Pass-through conditionals**: `.andEnsure()` \u0026 `.orEnsure()` methods.\n- **Sync \u0026 Async feature parity**: `Result\u003cT, E\u003e` and `Task\u003cT, E\u003e` provide the\n  same API for composing operations. Only the predicates `.isOk()` and\n  `.isErr()` are not implemented on `Task\u003cT, E\u003e` (for obvious reasons).\n- **Composability helpers**: Higher order `.lift()` and `.liftFallible()`\n  functions. Eliminating the need to manually wrap library or existing code in\n  many situations.\n- **Collection helpers**: Exposed via the namespaces `Options`, `Results` and\n  `Tasks`, every abstraction provides functions to collect tuples, arrays and\n  iterables into the base abstraction.\n\n### Option\n\n1. [Overview and factories](https://deno.land/x/eitherway/mod.ts?s=Option)\n2. [Base interface](https://deno.land/x/eitherway/mod.ts?s=IOption) implemented\n   by `Some\u003cT\u003e` and `None`\n3. [Collection helpers](https://deno.land/x/eitherway/mod.ts?s=Options)\n\n### Result\n\n1. [Overview and factories](https://deno.land/x/eitherway/mod.ts?s=Result)\n2. [Base interface](https://deno.land/x/eitherway/mod.ts?s=IResult) implemented\n   by `Ok\u003cT\u003e` and `Err\u003cE\u003e`\n3. [Collection helpers](https://deno.land/x/eitherway/mod.ts?s=Results)\n\n### Task\n\n1. [Overview and factories](https://deno.land/x/eitherway/mod.ts?s=Task)\n2. [Collection helpers](https://deno.land/x/eitherway/mod.ts?s=Tasks)\n\n## Best Practices\n\n1. **Computations - not data**: The abstractions provided by `eitherway` are\n   meant to represent the results of computations, not data.\n2. **Embrace immutability**: Don't mutate your state. That's it.\n3. **Return early occasionally**: When building up longer pipelines of\n   operations, especially if they involve synchronous and asynchronous\n   operations, you may want to break out of a pipeline to not enqueue\n   micro-tasks needlessly. The need to do this, does arise less frequently than\n   one might think though.\n4. **Unwrap at the edges**: Most application frameworks and library consumers\n   expect that any errors are propagated through exceptions (and hopefully\n   documented). Therefore, it's advised to unwrap `Option`s, `Result`s and\n   `Task`s at the outer most layer of your code. In a simple CRUD application,\n   this might be an error handling interceptor, a controller, or the\n   implementation of your public API in case of a library.\n5. **Some errors are unrecoverable**: In certain situations the \"errors are\n   values\" premise falls short on practicality.\n   [burntsushi](https://blog.burntsushi.net/unwrap/),\n   [Rob Pike](https://go.dev/doc/effective_go#errors) and many others have\n   already written extensively about this. When encountering a truly\n   unrecoverable error or an impossible state, throwing a runtime exception is a\n   perfectly valid solution.\n6. **Discriminate but don't over-accumulate**: It's often very tempting, to just\n   accumulate possible errors as a discriminated union when building out flows\n   via composition of `Result` and `Task` pipelines and let the user or the next\n   component in line figure out what to do next. This only works up to a certain\n   point. Errors are important domain objects, and they should be modeled\n   accordingly.\n7. **Lift others up to help yourself out**: Use the\n   [composability helpers](https://deno.land/x/eitherway/mod.ts?s=Result.liftFallible).\n   They really reduce noise and speed up integrating external code a lot.\n\n```typescript\nimport { Option, Result } from \"https://deno.land/x/eitherway/mod.ts\";\nimport * as semver from \"https://deno.land/std@0.206.0/semver/mod.ts\";\n\nconst noInputProvidedError = Error(\"No input provided\");\nconst toParseError = (e: unknown) =\u003e\n  TypeError(\"Could not parse version\", { cause: e });\n\nconst tryParse = Result.liftFallible(\n  semver.parse,\n  toParseError,\n);\n\nconst version = Option.from(Deno.args[0])\n  .okOr(noInputProvidedError)\n  .andThen(tryParse);\n```\n\n## FAQ\n\n\u003cdetails\u003e\n  \u003csummary\u003e\u003cb\u003eQ: Why should I even use something like this?\u003c/b\u003e\u003c/summary\u003e\n  \u003cb\u003eA: It's nice. Really.\u003c/b\u003e\n\nExplicit error types and built-in happy/error path selectors lead to expressive\ncode which is often even more pleasant to read.\n\n\u003cdetails\u003e\n    \u003csummary\u003eCompare these examples, taken from the benchmark suite:\u003c/summary\u003e\n\nSynchronous:\n\n```typescript\n/* Classic exception style */\n\ndeclare function toUpperCase(input: string | undefined): string;\ndeclare function stringToLength(input: string): number;\ndeclare function powerOfSelf(input: number): number;\n\nfunction processString(input: string | undefined): number {\n  try {\n    const upperCased = toUpperCase(input);\n    const length = stringToLength(upperCased);\n    return powerOfSelf(length);\n  } catch (error: unknown) {\n    if (error instanceof TypeError) {\n      console.error(error.message);\n      throw error;\n    }\n    throw new TypeError(\"Unknown error\", { cause: error });\n  }\n}\n```\n\n```typescript\n/* Equivalent Result flow */\n\nimport { Result } from \"https://deno.land/x/eitherway/mod.ts\";\n\ndeclare function toUpperCase(\n  input: string | undefined,\n): Result\u003cstring, TypeError\u003e;\ndeclare function stringToLength(input: string): Result\u003cnumber, TypeError\u003e;\ndeclare function powerOfSelf(input: number): Result\u003cnumber, TypeError\u003e;\n\nfunction processString(input: string | undefined): Result\u003cnumber, TypeError\u003e {\n  return toUpperCase(input)\n    .andThen(stringToLength)\n    .andThen(powerOfSelf)\n    .inspectErr((e) =\u003e console.error(e.message));\n}\n```\n\nAsynchronous:\n\n```typescript\n/* Classic exception style */\n\ndeclare function toUpperCase(input: string | undefined): Promise\u003cstring\u003e;\ndeclare function stringToLength(input: string): Promise\u003cnumber\u003e;\ndeclare function powerOfSelf(input: number): Promise\u003cnumber\u003e;\n\nasync function processString(input: string | undefined): Promise\u003cnumber\u003e {\n  try {\n    const upperCased = await toUpperCase(input);\n    const length = await stringToLength(upperCased);\n    return await powerOfSelf(length);\n  } catch (error: unknown) {\n    if (error instanceof TypeError) {\n      console.error(error.message);\n      throw error;\n    }\n    throw new TypeError(\"Unknown error\", { cause: error });\n  }\n}\n```\n\n```typescript\n/* Equivalent Task flow */\n\nimport { Result, Task } from \"https://deno.land/x/eitherway/mod.ts\";\n\ndeclare function toUpperCase(\n  input: string | undefined,\n): Task\u003cstring, TypeError\u003e;\ndeclare function stringToLength(\n  input: string,\n): Promise\u003cResult\u003cnumber, TypeError\u003e\u003e;\ndeclare function powerOfSelf(input: number): Task\u003cnumber, TypeError\u003e;\n\nfunction processString(input: string | undefined): Task\u003cnumber, TypeError\u003e {\n  return toUpperCase(input)\n    .andThen(stringToLength)\n    .andThen(powerOfSelf)\n    .inspectErr((e) =\u003e console.error(e.message));\n}\n```\n\n\u003c/details\u003e\n\nApart from making error cases explicit, the abstractions provided here foster a\ncode style, which naturally builds up complex computations via composition of\nsmall, focused functions/methods, where boundaries are defined by values. Thus\nleading to a highly maintainable and easily testable code base.\n\nEven better: These abstractions come with practically no overhead (see the next\nsection).\n\nHere are a couple of videos, explaining the general benefits in more detail:\n\n- [\"Railway-oriented programming\" by Scott Wlaschin](https://vimeo.com/113707214)\n- [\"Boundaries\" by Gary Bernhardt](https://www.destroyallsoftware.com/talks/boundaries)\n\n\u003c/details\u003e\n\n\u003cdetails\u003e\n  \u003csummary\u003e\u003cb\u003eQ: What is the performance impact of using this?\u003c/b\u003e\u003c/summary\u003e\n  \u003cb\u003eA: Practically none.\u003c/b\u003e\n\nYou can run the benchmark suite yourself with `$ deno bench`.\n\nThe benchmark results suggest, that for nearly all practical considerations\nthere is no or virtually no overhead of using the abstractions provided by\n`eitherway` vs. a classic exception propagation style.\n\nAlthough the result and task flows were slightly faster in the runs below, it's\nimportant not to fall into a micro-optimization trap. The conclusion should not\nnecessarily be \"use eitherway, it's faster\", but rather \"use eitherway, it's\npractically free\".\n\nThe overall performance thesis is that by returning errors instead of throwing,\ncatching and re-throwing exceptions, the instantiation costs of the abstractions\nprovided here are amortized over call-stack depth \u0026 it's size, as well as the\noptimizations the linear return path allows, sometimes even leading to small\nperformance improvements. This sounds plausible, and the results are not\nrefuting the null hypothesis here, but benchmarking is hard and for most use\ncases, the difference really won't matter.\n\n\u003cdetails\u003e\n    \u003csummary\u003eSynchronous exception propagation vs. result chaining\u003c/summary\u003e\n\n```markdown\ncpu: Intel(R) Core(TM) i9-9880H CPU @ 2.30GHz runtime: deno 1.33.2\n(x86_64-apple-darwin)\n\n## file:///projects/eitherway/bench/sync_bench.ts benchmark time (avg) (min … max) p75 p99 p995\n\nSyncExceptions 29.15 µs/iter (20.54 µs … 472 µs) 31.34 µs 38.22 µs 49.28 µs\nSyncResultFlow 15.49 µs/iter (11.07 µs … 441.17 µs) 15.44 µs 31.69 µs 43.37 µs\n\nsummary SyncResultFlow 1.88x faster than SyncExceptions\n```\n\n\u003c/details\u003e\n\n\u003cdetails\u003e\n    \u003csummary\u003eAsynchronous exception propagation vs. task chaining\u003c/summary\u003e\n\n```markdown\ncpu: Intel(R) Core(TM) i9-9880H CPU @ 2.30GHz runtime: deno 1.33.2\n(x86_64-apple-darwin)\n\n## file:///projects/eitherway/bench/async_bench.ts benchmark time (avg) (min … max) p75 p99 p995\n\nAsyncExceptions 24.78 ms/iter (22.08 ms … 25.55 ms) 25.46 ms 25.55ms 25.55ms\nTaskInstanceFlow 23.88 ms/iter (21.28 ms … 25.8 ms) 24.57 ms 25.8ms 25.8ms\nTaskOperatorFlow 24.21 ms/iter (21.33 ms … 25.73 ms) 25.36 ms 25.73ms 25.73ms\nTaskEarlyReturnFlow 24.04 ms/iter (20.36 ms … 25.47 ms) 25.42 ms 25.47ms 25.47ms\n\nsummary TaskInstanceFlow 1.01x faster than TaskEarlyReturnFlow 1.01x faster than\nTaskOperatorFlow 1.04x faster than AsyncExceptions\n```\n\n\u003c/details\u003e\n\n\u003cdetails\u003e\n    \u003csummary\u003eMicro benchmarks\u003c/summary\u003e\nIf you have a highly performance sensitive use case, you should be using\na different language.\nOn a more serious note, also small costs can add up and as a user, you should\nknow how high the costs are. So here are a few micro benchmarks:\n\n```markdown\ncpu: Intel(R) Core(TM) i9-9880H CPU @ 2.30GHz runtime: deno 1.33.2\n(x86_64-apple-darwin)\n\n## file:///projects/eitherway/bench/micro_bench.ts benchmark time (avg) (min … max) p75 p99 p995\n\nPromise.resolve(Ok) 44.33 ns/iter (35.81 ns … 106.41 ns) 44.6 ns 62.58 ns\n72.56ns Task.succeed 105.43 ns/iter (88.44 ns … 227.26 ns) 108.97 ns 204.75 ns\n212.54ns Promise.resolve(Err) 3.11 µs/iter (3.06 µs … 3.27 µs) 3.13 µs 3.27 µs\n3.27 µs Task.fail 2.94 µs/iter (2.71 µs … 3.35 µs) 3.25 µs 3.35 µs 3.35 µs\n\nsummary Promise.resolve(Ok) 2.38x faster than Task.succeed 66.41x faster than\nTask.fail 70.14x faster than Promise.resolve(Err)\n\n## file:///projects/eitherway/bench/micro_bench.ts benchmark time (avg) (min … max) p75 p99 p995\n\nOk 5.1 ns/iter (4.91 ns … 22.27 ns) 5.02 ns 8.62 ns 11.67 ns Err 4.88 ns/iter\n(4.7 ns … 17.93 ns) 4.81 ns 8.18 ns 10.52 ns Option 90.39 ns/iter (83.63 ns …\n172.61 ns) 93.31 ns 135.19 ns 146.79 ns\n\nsummary Err 1.05x faster than Ok 18.52x faster than Option\n\n## file:///projects/eitherway/bench/micro_bench.ts benchmark time (avg) (min … max) p75 p99 p995\n\nAsync Exception Propagation 9.08 µs/iter (8.95 µs … 9.26 µs) 9.18 µs 9.26 µs\n9.26 µs Async Error Propagation 6.32 µs/iter (6.24 µs … 6.52 µs) 6.37 µs 6.52 µs\n6.52 µs\n\nsummary Async Error Propagation 1.44x faster than Async Exception Propagation\n```\n\n\u003c/details\u003e\n\u003c/details\u003e\n\n\u003cdetails\u003e\n  \u003csummary\u003e\u003cb\u003eQ: Why can't I use Task\u003cT, E\u003e as the return type of an async function?\u003c/b\u003e\u003c/summary\u003e\n  \u003cb\u003eA: That's a general restriction of JavaScript.\u003c/b\u003e\n\nA function defined with the `async` keyword, must return a \"system\" `Promise`.\nAlthough `Task\u003cT, E\u003e` (currently) is a proper subclass of `Promise`, it cannot\nbe used in the Return Type Position of an async function, because it's _NOT_ a\n\"system\" promise (for lack of a better word).\n\nSince `Task\u003cT, E\u003e` is a subclass of `Promise\u003cResult\u003cT, E\u003e\u003e`, it's possible to\nreturn it as such from an async function though or just await it.\n\n```typescript\nimport { Result, Task } from \"https://deno.land/x/eitherway/mod.ts\";\n\nasync function toTask(str: string): Promise\u003cResult\u003cstring, never\u003e\u003e {\n  return Task.succeed(str);\n}\n```\n\nFurthermore, `Task\u003cT, E\u003e` is merely a composability extension for\n`Promise\u003cResult\u003cT, E\u003e\u003e`. As such, you can cheaply convert every\n`Promise\u003cResult\u003cT, E\u003e` via the `Task.of()` constructor, or use the promise\noperators to compose your pipeline.\n\n\u003c/details\u003e\n\n\u003cdetails\u003e\n  \u003csummary\u003e\u003cb\u003eQ: Why subclassing Promises instead of just providing a PromiseLike abstraction?\u003c/b\u003e\u003c/summary\u003e\n  \u003cb\u003eA: For compatibility reasons.\u003c/b\u003e\n\nThe drawback of the current implementation is that we cannot evaluate\n`Task\u003cT, E\u003e` lazily. On the other hand, a lot of framework or library code is\nstill (probably needlessly) invariant over `PromiseLike` types. Therefore\nsubclassing the native `Promise` and allowing the users to treat\n`Promise\u003cResult\u003cT, E\u003e\u003e` and `Task\u003cT, E\u003e` interchangeably in most situations, was\nthe preferred solution.\n\n\u003c/details\u003e\n\n## Prior Art\n\n- [neverthrow](https://github.com/supermacro/neverthrow)\n- [ts-result](https://github.com/vultix/ts-results)\n- [oxide.ts](https://github.com/traverse1984/oxide.ts)\n- [eventual-result](https://github.com/alexlafroscia/eventual-result)\n\n## License and Contributing\n\n### Contributing\n\n\u003cp\u003ePlease see \u003ca href=\"CONTRIBUTING.md\"\u003eCONTRIBUTING\u003c/a\u003e for more information.\u003c/p\u003e\n\n### License\n\n\u003cp\u003eLicensed under \u003ca href=\"LICENSE.md\"\u003eMIT license\u003c/a\u003e.\u003c/p\u003e\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frealpha%2Feitherway","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Frealpha%2Feitherway","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frealpha%2Feitherway/lists"}