Skip to content

Commit

Permalink
Merge pull request #1 from yifanwww/resultify
Browse files Browse the repository at this point in the history
feat: add resultify helper functions
  • Loading branch information
yifanwww committed Sep 16, 2023
2 parents 12341c3 + 5b208f1 commit da5d77e
Show file tree
Hide file tree
Showing 4 changed files with 279 additions and 1 deletion.
40 changes: 39 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ The Rust-like `Result` implements the following methods:
| orElse | [or_else] |
| transpose | [transpose] |

Unlike Rust, JavaScript doesn't have the 'Ownership' feature, so some API like `as_ref` is not necessary. These implementations are not implemented:
Unlike Rust, JavaScript doesn't have the 'Ownership' feature, so some API like `as_ref` are not necessary. These implementations are not implemented:

```md
<!-- implementations -->
Expand Down Expand Up @@ -129,6 +129,44 @@ There is a [proposal] (stage 2) that introduces `Record` and `Tuple` which are c

[proposal]: https://github.com/tc39/proposal-record-tuple

## More Helper Functions
### resultify

Takes a function and returns a version that returns results asynchronously.

```ts
import fs from 'node:fs/promises';

const copyFile1 = resultify(fs.copyFile);
const copyFile2 = resultify<Error>()(fs.copyFile);
```

### resultify.sync

Takes a function and returns a version that returns results synchronously.

```ts
/**
* @throws {Error} Some error messages
*/
function fn(): string {
// do something
}

const fn1 = resultify.sync(fn);
const fn1 = resultify.sync<Error>()(fn);
```

In the context where async functions are not allowed, you can use this function to resultify the sync function.

### resultify.promise

Takes a promise and returns a new promise that contains a result.

```ts
const result = await resultify.promise(promise);
```

## License

MIT
113 changes: 113 additions & 0 deletions src/__tests__/resultify.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
import { resultify } from '../resultify';

function syncFn(throws: boolean) {
if (throws) {
throw new Error('Some error message');
} else {
return 'hello world';
}
}

async function asyncFn(throws: boolean) {
if (throws) {
return Promise.reject(new Error('Some error message'));
} else {
return Promise.resolve('hello world');
}
}

describe(`Test fn \`${resultify.name}\``, () => {
it('should resultify a sync function', async () => {
const fn = resultify(syncFn);

const [result1, result2] = await Promise.all([fn(false), fn(true)]);

expect(result1.isOk()).toBe(true);
expect(result1.unwrap()).toBe('hello world');
expect(result2.isErr()).toBe(true);
expect(result2.unwrapErr()).toBeInstanceOf(Error);
expect((result2.unwrapErr() as Error).message).toBe('Some error message');
});

it('should resultify an async function', async () => {
const fn = resultify(asyncFn);

const [result1, result2] = await Promise.all([fn(false), fn(true)]);

expect(result1.isOk()).toBe(true);
expect(result1.unwrap()).toBe('hello world');
expect(result2.isErr()).toBe(true);
expect(result2.unwrapErr()).toBeInstanceOf(Error);
expect((result2.unwrapErr() as Error).message).toBe('Some error message');
});

it('should resultify a sync function with error type specified', async () => {
const fn = resultify<Error>()(syncFn);

const [result1, result2] = await Promise.all([fn(false), fn(true)]);

expect(result1.isOk()).toBe(true);
expect(result1.unwrap()).toBe('hello world');
expect(result2.isErr()).toBe(true);
expect(result2.unwrapErr()).toBeInstanceOf(Error);
expect(result2.unwrapErr().message).toBe('Some error message');
});

it('should resultify an async function with error type specified', async () => {
const fn = resultify<Error>()(asyncFn);

const [result1, result2] = await Promise.all([fn(false), fn(true)]);

expect(result1.isOk()).toBe(true);
expect(result1.unwrap()).toBe('hello world');
expect(result2.isErr()).toBe(true);
expect(result2.unwrapErr()).toBeInstanceOf(Error);
expect(result2.unwrapErr().message).toBe('Some error message');
});
});

describe(`Test fn \`${resultify.sync.name}\``, () => {
it('should resultify a sync function', () => {
const fn = resultify.sync(syncFn);

const result1 = fn(false);
const result2 = fn(true);

expect(result1.isOk()).toBe(true);
expect(result1.unwrap()).toBe('hello world');
expect(result2.isErr()).toBe(true);
expect(result2.unwrapErr()).toBeInstanceOf(Error);
expect((result2.unwrapErr() as Error).message).toBe('Some error message');
});

it('should resultify a sync function with error type specified', () => {
const fn = resultify.sync<Error>()(syncFn);

const result1 = fn(false);
const result2 = fn(true);

expect(result1.isOk()).toBe(true);
expect(result1.unwrap()).toBe('hello world');
expect(result2.isErr()).toBe(true);
expect(result2.unwrapErr()).toBeInstanceOf(Error);
expect(result2.unwrapErr().message).toBe('Some error message');
});
});

describe(`Test fn \`${resultify.promise.name}\``, () => {
it('should resultify a promise', async () => {
const promise1 = asyncFn(false);
const promise2 = asyncFn(true);

const [result1, result2] = await Promise.all([
resultify.promise<string, Error>(promise1),
resultify.promise<string, Error>(promise2),
]);

expect(result1.isOk()).toBe(true);
expect(result1.unwrap()).toBe('hello world');
expect(result2.isErr()).toBe(true);
expect(result2.unwrapErr()).toBeInstanceOf(Error);
expect(result2.unwrapErr().message).toBe('Some error message');
});
});
1 change: 1 addition & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
export * from './factory';
export * from './resultify';
export * from './types';
126 changes: 126 additions & 0 deletions src/resultify.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
import { Err, Ok } from './factory';
import type { Result } from './types';

type NoVoid<T> = T extends void ? undefined : T;

/**
* Takes a promise and returns a new promise that contains a result.
*
* Examples:
* ```ts
* const result = await resultify.promise(promise);
* ```
*/
async function resultifyPromise<T, E>(promise: Promise<T>): Promise<Result<NoVoid<T>, E>> {
try {
return Ok((await promise) as NoVoid<T>);
} catch (err) {
return Err(err as E);
}
}

type CurriedResultifySync<E> = <T, Args extends unknown[]>(
fn: (...args: Args) => T,
) => (...args: Args) => Result<NoVoid<T>, E>;

/**
* Takes a function and returns a version that returns results synchronously.
*
* Examples:
* ```ts
* function fn(): string {
* // throws error if failed
* }
* const fn1 = resultify.sync(fn);
* ```
*
* In the context where async functions are not allowed, you can use this function to resultify the sync function.
* If you want to resultify an async function, please use `resultify` instead.
*
* If you need the error value and want to specify its type, please use another overloaded function.
*/
function resultifySync<T, E, Args extends unknown[]>(fn: (...args: Args) => T): (...args: Args) => Result<NoVoid<T>, E>;
/**
* Takes a function and returns a version that returns results synchronously.
*
* Examples:
* ```ts
* function fn(): string {
* // throws error if failed
* }
* const fn1 = resultify.sync(fn);
* ```
*
* In the context where async functions are not allowed, you can use this function to resultify the sync function.
* If you want to resultify an async function, please use `resultify` instead.
*/
function resultifySync<E>(): CurriedResultifySync<E>;

function resultifySync<T, E, Args extends unknown[]>(
fn?: (...args: Args) => T,
): CurriedResultifySync<E> | ((...args: Args) => Result<NoVoid<T>, E>) {
function curriedResultify<TT, TArgs extends unknown[]>(_fn: (...args: TArgs) => TT) {
return function resultifiedFn(...args: TArgs): Result<NoVoid<TT>, E> {
try {
return Ok(_fn(...args) as NoVoid<TT>);
} catch (err) {
return Err(err as E);
}
};
}

return fn ? curriedResultify(fn) : curriedResultify;
}

type CurriedResultify<E> = <T, Args extends unknown[]>(
fn: (...args: Args) => T | Promise<T>,
) => (...args: Args) => Promise<Result<NoVoid<Awaited<T>>, E>>;

/**
* Takes a function and returns a version that returns results asynchronously.
*
* Examples:
* ```ts
* import fs from 'node:fs/promises';
*
* const copyFile = resultify(fs.copyFile);
* ```
*
* If you need the error value and want to specify its type, please use another overloaded function.
*/
function resultify<T, E, Args extends unknown[]>(
fn: (...args: Args) => T | Promise<T>,
): (...args: Args) => Promise<Result<NoVoid<Awaited<T>>, E>>;
/**
* Takes a function and returns a version that returns results asynchronously.
* This overloaded function allows you to specify the error type.
*
* Examples:
* ```ts
* import fs from 'node:fs/promises';
*
* const copyFile = resultify<Error>()(fs.copyFile);
* ```
*/
function resultify<E>(): CurriedResultify<E>;

function resultify<T, E, Args extends unknown[]>(
fn?: (...args: Args) => T | Promise<T>,
): CurriedResultify<E> | ((...args: Args) => Promise<Result<NoVoid<Awaited<T>>, E>>) {
function curriedResultify<TT, TArgs extends unknown[]>(_fn: (...args: TArgs) => TT | Promise<TT>) {
return async function resultifiedFn(...args: TArgs): Promise<Result<NoVoid<Awaited<TT>>, E>> {
try {
return Ok((await _fn(...args)) as NoVoid<Awaited<TT>>);
} catch (err) {
return Err(err as E);
}
};
}

return fn ? curriedResultify(fn) : curriedResultify;
}

resultify.sync = resultifySync;
resultify.promise = resultifyPromise;

export { resultify };

0 comments on commit da5d77e

Please sign in to comment.