diff --git a/either/README.md b/either/README.md index 0db0b47..3ed8158 100644 --- a/either/README.md +++ b/either/README.md @@ -214,8 +214,18 @@ function fromTry(fn: () => R): Either; fromTry(() => 2); // Either.Right fromTry(() => { throw new Error("test"); -}); // Either.Left +}); // Either.Left + +// enforcing error type +fromTry(() => { + const result = parseInt(value, 10); + if (Number.isNaN(result)) { + throw new ParsingError('Invalid number'); + } + return result; +}) ``` +Enforcing error types might be error-prone. [Read more](#enforcing-error-types) about it. #### `fromPromise` @@ -227,9 +237,14 @@ function fromPromise(promise: Promise): Promise>; ```typescript fromPromise(Promise.resolve(2)); // Either.Right -fromPromise(Promise.reject(new Error("test"))); // Either.Left +fromPromise(Promise.reject(new Error("test"))); // Either.Left + +// enforcing error type +fromPromise(Promise.resolve(2)); ``` +Enforcing error types might be error-prone. [Read more](#enforcing-error-types) about it. + #### `isEither` ```typescript @@ -521,6 +536,71 @@ left(2).unwrapOrElse(num => num * 2) // returns 4 right(2).unwrapOrElse(num => num * 2) // returns 2 ``` +#### Enforcing error types + +Typescript cannot detect type of thrown errors or rejected promises. + +```typescript +class ParsingError { + constructor(readonly message: string) {} +} + +// type is Either but in theory should be Either +const newVal1 = fromTry(() => { + if (Math.random() > 0.5) { + return 10; + } + throw new ParsingError('Error') +}); + +// type is Either but in theory should be Either +const newVal2 = fromPromise(Promise.reject(new ParsingError('Error'))) +``` + +Having that on might you might sometimes need to add a hint for Typescript. +```typescript +// Either +const newVal1 = fromTry(() => { + if (Math.random() > 0.5) { + return 10; + } + throw new ParsingError('Error') +}); + +// Either.Left +const newVal2 = fromPromise(() => { + throw new Error("test"); +}); +``` + +This might be error-prone. +You might change type of thrown error and forgot to change hint for typescript. In that case Typescript will never spot the error. +```typescript +const newVal1 = fromTry(() => { // invalid type hint + if (Math.random() > 0.5) { + return 10; + } + throw new AnotherErrorType('Error') +}); +``` + +That is why it is safer to `mapLeft` thrown errors with type guards. +```typescript +// Either +const newVal1 = fromTry(() => { + if (Math.random() > 0.5) { + return 10; + } + throw new AnotherErrorType(); +}) + .mapLeft(x=> { + if (x instanceof AnotherErrorType) { + return x; + } + throw new Error('Invalid error type thrown'); + }) +``` + ## License MIT (c) Artem Kobzar see LICENSE file. diff --git a/either/index.ts b/either/index.ts index d072ac7..424d7c3 100644 --- a/either/index.ts +++ b/either/index.ts @@ -24,6 +24,7 @@ type StaticCheck = ClassImplements< typeof EitherConstructor, [MonadConstructor, ApplicativeConstructor, AsyncChainable>] >; + class EitherConstructor implements AsyncMonad, Alternative, Container { static chain(f: (v: R) => Promise>): (m: Either) => Promise>; @@ -193,11 +194,11 @@ class EitherConstructor return EitherConstructor.right(v); } - static fromPromise(promise: Promise): Promise> { + static fromPromise(promise: Promise): Promise> { return promise.then(EitherConstructor.right).catch(EitherConstructor.left); } - static fromTry(fn: () => R): Either { + static fromTry(fn: () => R): Either { try { return EitherConstructor.right(fn()); } catch (e) {