From fd83139ed5fb3688d6f8d8b913b80578b27c34c5 Mon Sep 17 00:00:00 2001 From: Anton Nesterov Date: Thu, 17 Oct 2024 05:24:06 +0200 Subject: [PATCH] add generator support --- README.md | 36 ++++++++++++++++++++++++++++ dist/grip.js | 48 +++++++++++++++++++++++++++++++++++-- grip.test.ts | 32 +++++++++++++++++++++++++ grip.ts | 68 +++++++++++++++++++++++++++++++++++++++++++++++----- 4 files changed, 176 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index c0c1f7a..5fd6a06 100644 --- a/README.md +++ b/README.md @@ -66,5 +66,41 @@ if (parseStatus.Of(SyntaxError)) { } ``` +## Handle functions: + +Grip can also handle functions: + +```javascript +const [result, status] = grip(() => "ok"); +// result = "ok" + +const [result1, status1] = grip(() => { + if (1) throw new Error("error") +}); +// result1 = null +// status.Of(Error) = true +``` + +## Handle generators + +Generators can be handled using the `Iter()` method: + +```javascript +const res = grip(async function* () { + for (let i = 0; i < 3; i++) { + if (i == 2) throw new Error("2"); + yield i; + } +}); +for await (let [value, status] of res.Iter()) { + if (status.Of(Error)) { + // handle error properly + break; + } + // typeof value === "number" + console.log(value) +} +``` + ## License MIT diff --git a/dist/grip.js b/dist/grip.js index 55c2491..0b4cdcb 100644 --- a/dist/grip.js +++ b/dist/grip.js @@ -67,13 +67,57 @@ class Result extends Array { Fail() { return this[1].Fail(); } - Of(impl) { - return this[1].Of(impl); + Of(cls) { + return this[1].Of(cls); + } + Iter() { + const value = this.value; + const that = this; + if (typeof value !== "object" && !(typeof value[Symbol.iterator] === "function" || typeof value[Symbol.asyncIterator] === "function")) { + return { + async* [Symbol.asyncIterator]() { + yield new Result(that.value, that.status); + }, + *[Symbol.iterator]() { + yield new Result(that.value, that.status); + } + }; + } + return { + async* [Symbol.asyncIterator]() { + yield* asyncIterator(value); + }, + *[Symbol.iterator]() { + yield* iterator(value); + } + }; } } var promise = (result) => { return result.then((res) => new Result(res, new Ok)).catch((err) => new Result(null, Err.fromCatch(err))); }; +var iterator = function* (iter) { + try { + let data = iter.next(); + while (!data.done) { + yield new Result(data.value, new Ok); + data = iter.next(); + } + } catch (e) { + yield new Result(null, Err.fromCatch(e)); + } +}; +var asyncIterator = async function* (iter) { + try { + let data = await iter.next(); + while (!data.done) { + yield new Result(data.value, new Ok); + data = await iter.next(); + } + } catch (e) { + yield new Result(null, Err.fromCatch(e)); + } +}; export { grip, Ok, diff --git a/grip.test.ts b/grip.test.ts index 8c49ee3..18abf17 100644 --- a/grip.test.ts +++ b/grip.test.ts @@ -63,3 +63,35 @@ test("fetch json", async () => { expect(jsonStatus.Of(SyntaxError)).toBe(true); expect(json === null).toBe(true); }); + +test("function*", async () => { + const res = grip(function* () { + for (let i = 0; i < 3; i++) { + if (i == 2) throw new Error("2"); + yield i; + } + }); + expect(res.Ok()).toBe(true); + for (let [value, status] of res.Iter()) { + if (status.Of(Error)) { + break; + } + expect(value).toBeTypeOf("number"); + } +}); + +test("async function*", async () => { + const res = grip(async function* () { + for (let i = 0; i < 3; i++) { + if (i == 2) throw new Error("2"); + yield i; + } + }); + expect(res.Ok()).toBe(true); + for await (let [value, status] of res.Iter()) { + if (status.Of(Error)) { + break; + } + expect(value).toBeTypeOf("number"); + } +}); diff --git a/grip.ts b/grip.ts index 45faab1..4c93e60 100644 --- a/grip.ts +++ b/grip.ts @@ -69,19 +69,51 @@ class Result extends Array implements IResult { Fail() { return (this[1] as Status).Fail(); } - Of(impl: any) { - return (this[1] as Status).Of(impl); + Of(cls: any) { + return (this[1] as Status).Of(cls); + } + Iter() { + const value = this.value; + const that = this; + if ( + typeof value !== "object" && + !( + typeof (value as any)[Symbol.iterator] === "function" || + typeof (value as any)[Symbol.asyncIterator] === "function" + ) + ) { + return { + async *[Symbol.asyncIterator]() { + yield new Result(that.value, that.status) as SafeResult; + }, + *[Symbol.iterator]() { + yield new Result(that.value, that.status) as SafeResult; + }, + }; + } + return { + async *[Symbol.asyncIterator]() { + yield* asyncIterator(value as AsyncGenerator); + }, + *[Symbol.iterator]() { + yield* iterator(value as Generator); + }, + }; } } type Unwrap = - T extends Promise + T extends AsyncGenerator ? U - : T extends (...args: any) => Promise + : T extends Generator ? U - : T extends (...args: any) => infer U + : T extends Promise ? U - : T; + : T extends (...args: any) => Promise + ? U + : T extends (...args: any) => infer U + ? U + : T; export type SafeResult = T extends Promise @@ -110,3 +142,27 @@ const promise = (result: Promise) => { .then((res) => new Result(res, new Ok())) .catch((err) => new Result(null, Err.fromCatch(err))) as Promise>; }; + +const iterator = function* (iter: Generator) { + try { + let data = iter.next(); + while (!data.done) { + yield new Result(data.value as T, new Ok()) as SafeResult; + data = iter.next(); + } + } catch (e) { + yield new Result(null as T, Err.fromCatch(e)) as SafeResult; + } +}; + +const asyncIterator = async function* (iter: AsyncGenerator) { + try { + let data = await iter.next(); + while (!data.done) { + yield new Result(data.value as T, new Ok()) as SafeResult; + data = await iter.next(); + } + } catch (e) { + yield new Result(null as T, Err.fromCatch(e)) as SafeResult; + } +};