From f9701175ec8a368f1281ae673b67684d728cfcc4 Mon Sep 17 00:00:00 2001 From: harris-miller Date: Thu, 2 Oct 2025 23:38:00 -0600 Subject: [PATCH 1/4] add ResultAsync.fromPromiseResult static method --- src/result-async.ts | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/result-async.ts b/src/result-async.ts index 20120d2b..5576580d 100644 --- a/src/result-async.ts +++ b/src/result-async.ts @@ -60,6 +60,13 @@ export class ResultAsync implements PromiseLike> { } } + // eslint-disable-next-line @typescript-eslint/no-explicit-any + static fromPromiseResult = ( + fn: (...args: A) => Promise>, + ) => (...args: A): ResultAsync => { + return ResultAsync.fromThrowable(fn, (e) => e as E)(...args).andThen((x) => x) + } + static combine< T extends readonly [ResultAsync, ...ResultAsync[]] >(asyncResultList: T): CombineResultAsyncs From f15a920195b25df12e45179c546dd5390e42bf2b Mon Sep 17 00:00:00 2001 From: harris-miller Date: Thu, 2 Oct 2025 23:38:24 -0600 Subject: [PATCH 2/4] alias --- src/result-async.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/result-async.ts b/src/result-async.ts index 5576580d..75c73550 100644 --- a/src/result-async.ts +++ b/src/result-async.ts @@ -267,6 +267,7 @@ export const fromPromise = ResultAsync.fromPromise export const fromSafePromise = ResultAsync.fromSafePromise export const fromAsyncThrowable = ResultAsync.fromThrowable +export const fromPromiseResult = ResultAsync.fromPromiseResult // Combines the array of async results into one result. export type CombineResultAsyncs< From 3a751c58a727707c8797e475abe4b86b97f3b0a0 Mon Sep 17 00:00:00 2001 From: harris-miller Date: Thu, 2 Oct 2025 23:58:40 -0600 Subject: [PATCH 3/4] update README.md --- README.md | 43 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/README.md b/README.md index cfe6b7be..dc6b7df9 100644 --- a/README.md +++ b/README.md @@ -49,6 +49,7 @@ For asynchronous tasks, `neverthrow` offers a `ResultAsync` class which wraps a - [`ResultAsync.fromThrowable` (static class method)](#resultasyncfromthrowable-static-class-method) - [`ResultAsync.fromPromise` (static class method)](#resultasyncfrompromise-static-class-method) - [`ResultAsync.fromSafePromise` (static class method)](#resultasyncfromsafepromise-static-class-method) + - [`ResultAsync.fromPromiseResult` (static class method)](#resultasyncfrompromiseresult-static-class-method) - [`ResultAsync.map` (method)](#resultasyncmap-method) - [`ResultAsync.mapErr` (method)](#resultasyncmaperr-method) - [`ResultAsync.unwrapOr` (method)](#resultasyncunwrapor-method) @@ -66,6 +67,7 @@ For asynchronous tasks, `neverthrow` offers a `ResultAsync` class which wraps a - [`fromAsyncThrowable`](#fromasyncthrowable) - [`fromPromise`](#frompromise) - [`fromSafePromise`](#fromsafepromise) + - [`fromPromiseResult`](#frompromiseresult) - [`safeTry`](#safetry) + [Testing](#testing) * [A note on the Package Name](#a-note-on-the-package-name) @@ -124,6 +126,7 @@ import { fromThrowable, fromPromise, fromSafePromise, + fromPromiseResult, safeTry, } from 'neverthrow' ``` @@ -1038,6 +1041,46 @@ export const signupHandler = route((req, sessionManager) => --- +#### `ResultAsync.fromPromiseResult` (static class method) + +`ResultAsync.fromPromiseResult` wraps a function returning `Promise>` returning function, returning a function with the same signature, expect returning an `ResultAsync`. This is particularly useful combined with `async` functions since you cannot return `ResultAsync` directly, though you desire to. + +**Signature:** + +```typescript +// fromPromiseResult is a static class method +// also available as a standalone function +// import { fromPromiseResult } from 'neverthrow' +ResultAsync.fromPromiseResult = ( + fn: (...args: A) => Promise>, + ) => (...args: A): ResultAsync +``` + +**Example**: + +```typescript +export const callApi = ResultAsync.fromPromiseResult(async (postId: number): Promise> => { + try { + const post = await fetch(`/api/posts/${id}`).then(resp => !resp.ok ? Promise.reject(resp) : resp); + const query = post.comments.map(c => c.commenterId).join(','); + const people = await fetch(`/api/people?ids=${query}`).then(resp => !resp.ok ? Promise.reject(resp) : resp); + return ok(people); + } catch(e) { + return err(e as Error); + } +}); + +const people: Person[] = await callApi(123) + .orElse(e => { + console.error(e); + return []; + }); +``` + +[⬆️ Back to top](#toc) + +--- + #### `ResultAsync.map` (method) Maps a `ResultAsync` to `ResultAsync` by applying a function to a contained `Ok` value, leaving an `Err` value untouched. From da73f3df982f264edb3e978b6e22ff74a5a645c9 Mon Sep 17 00:00:00 2001 From: harris-miller Date: Fri, 3 Oct 2025 00:08:10 -0600 Subject: [PATCH 4/4] update readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index dc6b7df9..6696544c 100644 --- a/README.md +++ b/README.md @@ -1059,7 +1059,7 @@ ResultAsync.fromPromiseResult = ( **Example**: ```typescript -export const callApi = ResultAsync.fromPromiseResult(async (postId: number): Promise> => { +export const callApi: ResultAsync = ResultAsync.fromPromiseResult(async (postId: number): Promise> => { try { const post = await fetch(`/api/posts/${id}`).then(resp => !resp.ok ? Promise.reject(resp) : resp); const query = post.comments.map(c => c.commenterId).join(',');