yield-result 0.1.4 β 0.1.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +25 -19
- package/dist/wrappers.d.ts +8 -0
- package/dist/wrappers.js +12 -0
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -45,6 +45,16 @@ const processOrder = (id: string) => safe.async(async function* () {
|
|
|
45
45
|
});
|
|
46
46
|
```
|
|
47
47
|
|
|
48
|
+
### π How `yield*` Works Under the Hood (Didactic Guide)
|
|
49
|
+
|
|
50
|
+
Many JavaScript developers assume generators are complex, but in `yield-result`, generator delegation (**`yield*`**) is simply a **native protocol for unwrapping values**:
|
|
51
|
+
|
|
52
|
+
1. **`Ok<T>` Protocol**: Implements `[Symbol.iterator]` to return `value` immediately without yielding anything. Therefore, `const x = yield* ok(10)` assigns `10` directly to `x` in 0 suspension steps.
|
|
53
|
+
2. **`Err<E>` Protocol**: Implements `[Symbol.iterator]` to `yield` the `Err` instance once back to `safe.sync` / `safe.async`.
|
|
54
|
+
3. **Short-Circuiting**: The runner (`safe.sync`) receives the `Err` object, immediately calls `gen.return()` (triggering any `try ... finally` cleanup in your function), and early-returns `err(E)`.
|
|
55
|
+
|
|
56
|
+
No exception throwing, no stack unwinding, 100% native ECMAScript iteration semantics.
|
|
57
|
+
|
|
48
58
|
---
|
|
49
59
|
|
|
50
60
|
## π Installation
|
|
@@ -55,30 +65,24 @@ npm install yield-result
|
|
|
55
65
|
|
|
56
66
|
---
|
|
57
67
|
|
|
58
|
-
##
|
|
59
|
-
|
|
60
|
-
Benchmarked using `vitest bench` (powered by `tinybench`) on Node.js:
|
|
61
|
-
|
|
62
|
-
### π΄ Unhappy Path (Error Triggered)
|
|
68
|
+
## π¬ Architectural Analysis & V8 Engine Optimization
|
|
63
69
|
|
|
64
|
-
|
|
70
|
+
### Why We Do Not Use `throw` Internally for Flow Control
|
|
65
71
|
|
|
66
|
-
|
|
67
|
-
| :--- | :---: | :--- |
|
|
68
|
-
| **`yield-result` (`safe.sync`)** | **`207,485 ops/sec`** | β‘ **3.55x FASTER than native try/catch** |
|
|
69
|
-
| **Native `try/catch` with `throw`** | **`58,515 ops/sec`** | π’ 3.55x slower |
|
|
72
|
+
While libraries like `neverthrow` are fantastic, their generator-based flow control (`safeTry`) relies on calling `.safeUnwrap()`, which **throws native exceptions internally** to unwind the generator stack back to a `catch` block.
|
|
70
73
|
|
|
71
|
-
|
|
72
|
-
|
|
74
|
+
In JavaScript V8 engines (Node.js, Chrome), throwing exceptions forces the engine to:
|
|
75
|
+
1. Pause the execution loop.
|
|
76
|
+
2. Capture full call stack frames.
|
|
77
|
+
3. Deoptimize JIT compiler loops.
|
|
73
78
|
|
|
74
|
-
|
|
79
|
+
**`yield-result` takes a zero-exception approach**:
|
|
80
|
+
Our generator runners (`safe.sync` / `safe.async` / `Result.gen`) rely **exclusively on ECMAScript native iteration protocols (`Symbol.iterator`)**.
|
|
75
81
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
| **Native `try/catch` (no throw)** | `11,372,962 ops/sec` | `0.0001 ms` |
|
|
79
|
-
| **`yield-result` (`safe.sync`)** | `231,068 ops/sec` | `0.0043 ms` |
|
|
82
|
+
- **`Ok<T>`** returns its value directly in 0 suspension steps.
|
|
83
|
+
- **`Err<E>`** yields the `Err` object once to the runner, which immediately triggers `gen.return()` and early-returns the `Err` value.
|
|
80
84
|
|
|
81
|
-
|
|
85
|
+
By avoiding internal `throw` statements entirely, `yield-result` keeps your execution pipeline 100% JIT-optimizable by the V8 engine under both happy and unhappy path conditions.
|
|
82
86
|
|
|
83
87
|
---
|
|
84
88
|
|
|
@@ -281,10 +285,12 @@ safe.sync(function* () {
|
|
|
281
285
|
* **`safe.sync(function* () { ... })`** / **`Result.gen(function* () { ... })`** β Executes a synchronous generator function with early-return short-circuiting on `Err`.
|
|
282
286
|
* **`safe.async(async function* () { ... })`** / **`Result.gen.async(async function* () { ... })`** β Executes an async generator function with early-return short-circuiting on `Err`.
|
|
283
287
|
|
|
284
|
-
### Exception Wrappers
|
|
288
|
+
### Exception Wrappers & Function Lifting
|
|
285
289
|
* **`fromThrowable(fn, errorMapper?)`** β Wraps a throwing function into a `Result<T, E>`.
|
|
286
290
|
* **`fromPromise(promise, errorMapper?)`** β Wraps a Promise into `Promise<Result<T, E>>`.
|
|
287
291
|
* **`fromAsyncFn(fn, errorMapper?)`** β Wraps an async function into `Promise<Result<T, E>>`.
|
|
292
|
+
* **`lift(fn, errorMapper?)`** β Lifts a sync throwing function `(...args) => T` into a Result function `(...args) => Result<T, E>`.
|
|
293
|
+
* **`liftAsync(fn, errorMapper?)`** β Lifts an async function `(...args) => Promise<T>` into a Promise<Result> function `(...args) => Promise<Result<T, E>>`.
|
|
288
294
|
|
|
289
295
|
### Combinators & Utilities
|
|
290
296
|
* **`map(result, fn)`** β Transforms the `Ok` value.
|
package/dist/wrappers.d.ts
CHANGED
|
@@ -14,3 +14,11 @@ export declare const fromPromise: <T, E = unknown>(promise: Promise<T>, errorMap
|
|
|
14
14
|
* Catches both synchronous exceptions thrown during execution and async rejections.
|
|
15
15
|
*/
|
|
16
16
|
export declare const fromAsyncFn: <T, E = unknown>(fn: () => Promise<T>, errorMapper?: (e: unknown) => E) => Promise<Result<T, E>>;
|
|
17
|
+
/**
|
|
18
|
+
* Lifts a standard synchronous function that may throw into a Result-returning function.
|
|
19
|
+
*/
|
|
20
|
+
export declare const lift: <A extends any[], T, E = unknown>(fn: (...args: A) => T, errorMapper?: (e: unknown) => E) => ((...args: A) => Result<T, E>);
|
|
21
|
+
/**
|
|
22
|
+
* Lifts an asynchronous function returning a Promise into a Promise<Result<T, E>> function.
|
|
23
|
+
*/
|
|
24
|
+
export declare const liftAsync: <A extends any[], T, E = unknown>(fn: (...args: A) => Promise<T>, errorMapper?: (e: unknown) => E) => ((...args: A) => Promise<Result<T, E>>);
|
package/dist/wrappers.js
CHANGED
|
@@ -37,3 +37,15 @@ export const fromAsyncFn = async (fn, errorMapper) => {
|
|
|
37
37
|
return err(errorMapper ? errorMapper(e) : e);
|
|
38
38
|
}
|
|
39
39
|
};
|
|
40
|
+
/**
|
|
41
|
+
* Lifts a standard synchronous function that may throw into a Result-returning function.
|
|
42
|
+
*/
|
|
43
|
+
export const lift = (fn, errorMapper) => {
|
|
44
|
+
return (...args) => fromThrowable(() => fn(...args), errorMapper);
|
|
45
|
+
};
|
|
46
|
+
/**
|
|
47
|
+
* Lifts an asynchronous function returning a Promise into a Promise<Result<T, E>> function.
|
|
48
|
+
*/
|
|
49
|
+
export const liftAsync = (fn, errorMapper) => {
|
|
50
|
+
return (...args) => fromAsyncFn(() => fn(...args), errorMapper);
|
|
51
|
+
};
|
package/package.json
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "yield-result",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.6",
|
|
4
4
|
"description": "Rust-style ? operator for TypeScript using generators (yield*). Zero dependencies, type-safe error handling.",
|
|
5
5
|
"type": "module",
|
|
6
|
+
"sideEffects": false,
|
|
6
7
|
"main": "./dist/index.js",
|
|
7
8
|
"module": "./dist/index.js",
|
|
8
9
|
"types": "./dist/index.d.ts",
|
|
@@ -46,9 +47,8 @@
|
|
|
46
47
|
],
|
|
47
48
|
"license": "MIT",
|
|
48
49
|
"devDependencies": {
|
|
50
|
+
"neverthrow": "^8.2.0",
|
|
49
51
|
"typescript": "^5.5.4",
|
|
50
52
|
"vitest": "^2.0.5"
|
|
51
53
|
}
|
|
52
54
|
}
|
|
53
|
-
|
|
54
|
-
|