Skip to content

Commit 51e0708

Browse files
Stdlib: Add async result helpers (#7906)
* Stdlib: Add mapOkAsync, mapErrorAsync, flatMapOkAsync and flatMapErrorAsync to result * Changelog * Update completions
1 parent b4e6b43 commit 51e0708

File tree

6 files changed

+245
-18
lines changed

6 files changed

+245
-18
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
#### :rocket: New Feature
2222

2323
- Add `littleEndian` feature for `DataView` to Stdlib. https://github.com/rescript-lang/rescript/pull/7881
24+
- Add `mapOkAsync`, `mapErrorAsync`, `flatMapOkAsync` and `flatMapErrorAsync` for async `result`'s to Stdlib. https://github.com/rescript-lang/rescript/pull/7906
2425

2526
#### :bug: Bug fix
2627

packages/@rescript/runtime/Stdlib_Result.res

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -174,3 +174,27 @@ let all6 = ((a, b, c, d, e, f)) => {
174174
}
175175

176176
external ignore: result<'res, 'err> => unit = "%ignore"
177+
178+
let mapOkAsync = async (res, f) =>
179+
switch await res {
180+
| Ok(value) => Ok(f(value))
181+
| Error(err) => Error(err)
182+
}
183+
184+
let mapErrorAsync = async (res, f) =>
185+
switch await res {
186+
| Ok(value) => Ok(value)
187+
| Error(err) => Error(f(err))
188+
}
189+
190+
let flatMapOkAsync = async (res, f) =>
191+
switch await res {
192+
| Ok(value) => await f(value)
193+
| Error(err) => Error(err)
194+
}
195+
196+
let flatMapErrorAsync = async (res, f) =>
197+
switch await res {
198+
| Ok(value) => Ok(value)
199+
| Error(err) => await f(err)
200+
}

packages/@rescript/runtime/Stdlib_Result.resi

Lines changed: 102 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -54,14 +54,14 @@ type t<'res, 'err> = result<'res, 'err> = Ok('res) | Error('err)
5454
Result.getExn(Result.Ok(42)) == 42
5555
5656
switch Result.getExn(Error("Invalid data")) {
57-
| exception _ => assert(true)
58-
| _ => assert(false)
59-
}
57+
| exception _ => true
58+
| _ => false
59+
} == true
6060
6161
switch Result.getExn(Error("Invalid data"), ~message="was Error!") {
62-
| exception _ => assert(true) // Throws a JsError with the message "was Error!"
63-
| _ => assert(false)
64-
}
62+
| exception _ => true // Throws a JsError with the message "was Error!"
63+
| _ => false
64+
} == true
6565
```
6666
*/
6767
@deprecated("Use 'getOrThrow' instead")
@@ -74,14 +74,14 @@ let getExn: (result<'a, 'b>, ~message: string=?) => 'a
7474
Result.getOrThrow(Result.Ok(42)) == 42
7575
7676
switch Result.getOrThrow(Error("Invalid data")) {
77-
| exception _ => assert(true)
78-
| _ => assert(false)
79-
}
77+
| exception _ => true
78+
| _ => false
79+
} == true
8080
8181
switch Result.getOrThrow(Error("Invalid data"), ~message="was Error!") {
82-
| exception _ => assert(true) // Throws a JsError with the message "was Error!"
83-
| _ => assert(false)
84-
}
82+
| exception _ => true // Throws a JsError with the message "was Error!"
83+
| _ => false
84+
} == true
8585
```
8686
*/
8787
let getOrThrow: (result<'a, 'b>, ~message: string=?) => 'a
@@ -259,8 +259,8 @@ let forEach: (result<'a, 'b>, 'a => unit) => unit
259259
260260
```rescript
261261
let format = n => `Error code: ${n->Int.toString}`
262-
Result.mapError(Error(14), format) // Error("Error code: 14")
263-
Result.mapError(Ok("abc"), format) // Ok("abc")
262+
Result.mapError(Error(14), format) == Error("Error code: 14")
263+
Result.mapError(Ok("abc"), format) == Ok("abc")
264264
```
265265
*/
266266
let mapError: (result<'a, 'b>, 'b => 'c) => result<'a, 'c>
@@ -269,8 +269,8 @@ let mapError: (result<'a, 'b>, 'b => 'c) => result<'a, 'c>
269269
`all(results)` returns a result of array if all options are Ok, otherwise returns Error.
270270
## Examples
271271
```rescript
272-
Result.all([Ok(1), Ok(2), Ok(3)]) // Ok([1, 2, 3])
273-
Result.all([Ok(1), Error(1)]) // Error(1)
272+
Result.all([Ok(1), Ok(2), Ok(3)]) == Ok([1, 2, 3])
273+
Result.all([Ok(1), Error(1)]) == Error(1)
274274
```
275275
*/
276276
let all: array<result<'a, 'b>> => result<array<'a>, 'b>
@@ -321,3 +321,89 @@ let all6: (
321321
without having to store or process it further.
322322
*/
323323
external ignore: result<'res, 'err> => unit = "%ignore"
324+
325+
/**
326+
`mapOkAsync(res, f)`: Asynchronously maps over the Ok value of a Result. When `res` is `Ok(n)`,
327+
applies the async function `f` to `n` and wraps the result in `Ok`.
328+
When `res` is `Error`, returns the error unchanged.
329+
330+
## Examples
331+
332+
```rescript
333+
let asyncSquare = async x => x * x
334+
335+
let result1 = Result.mapOkAsync(Promise.resolve(Ok(4)), asyncSquare) // Returns promise that resolves to Ok(16)
336+
let result2 = Result.mapOkAsync(Promise.resolve(Error("invalid")), asyncSquare) // Returns promise that resolves to Error("invalid")
337+
```
338+
*/
339+
let mapOkAsync: (
340+
promise<result<'ok, 'error>>,
341+
'ok => 'mappedOk,
342+
) => promise<result<'mappedOk, 'error>>
343+
344+
/**
345+
`mapErrorAsync(res, f)`: Asynchronously maps over the Error value of a Result. When `res` is `Error(e)`,
346+
applies the async function `f` to `e` and wraps the result in `Error`.
347+
When `res` is `Ok`, returns the ok value unchanged.
348+
349+
## Examples
350+
351+
```rescript
352+
let asyncFormatError = async e => `Error: ${e}`
353+
354+
let result1 = Result.mapErrorAsync(Promise.resolve(Ok(42)), asyncFormatError) // Returns promise that resolves to Ok(42)
355+
let result2 = Result.mapErrorAsync(Promise.resolve(Error("invalid")), asyncFormatError) // Returns promise that resolves to Error("Error: invalid")
356+
```
357+
*/
358+
let mapErrorAsync: (
359+
promise<result<'ok, 'error>>,
360+
'error => 'mappedError,
361+
) => promise<result<'ok, 'mappedError>>
362+
363+
/**
364+
`flatMapOkAsync(res, f)`: Asynchronously flat-maps over the Ok value of a Result. When `res` is `Ok(n)`,
365+
applies the async function `f` to `n` which returns a Promise of a Result.
366+
When `res` is `Error`, returns the error unchanged.
367+
368+
## Examples
369+
370+
```rescript
371+
let asyncValidate = async x =>
372+
if x > 0 {
373+
Ok(x * 2)
374+
} else {
375+
Error("Must be positive")
376+
}
377+
378+
let result1 = Result.flatMapOkAsync(Promise.resolve(Ok(5)), asyncValidate) // Returns promise that resolves to Ok(10)
379+
let result2 = Result.flatMapOkAsync(Promise.resolve(Error("Already failed")), asyncValidate) // Returns promise that resolves to Error("Already failed")
380+
```
381+
*/
382+
let flatMapOkAsync: (
383+
promise<result<'ok, 'error>>,
384+
'ok => promise<result<'mappedOk, 'error>>,
385+
) => promise<result<'mappedOk, 'error>>
386+
387+
/**
388+
`flatMapErrorAsync(res, f)`: Asynchronously flat-maps over the Error value of a Result. When `res` is `Error(e)`,
389+
applies the async function `f` to `e` which returns a Promise of a Result.
390+
When `res` is `Ok`, returns the ok value unchanged.
391+
392+
## Examples
393+
394+
```rescript
395+
let asyncRecover = async error =>
396+
if error === "timeout" {
397+
Ok("default")
398+
} else {
399+
Error(error)
400+
}
401+
402+
let result1 = Result.flatMapErrorAsync(Promise.resolve(Error("timeout")), asyncRecover) // Returns promise that resolves to Ok("default")
403+
let result2 = Result.flatMapErrorAsync(Promise.resolve(Ok("default")), asyncRecover) // Returns promise that resolves to Ok("default")
404+
```
405+
*/
406+
let flatMapErrorAsync: (
407+
promise<result<'ok, 'error>>,
408+
'error => promise<result<'ok, 'mappedError>>,
409+
) => promise<result<'ok, 'mappedError>>

packages/@rescript/runtime/lib/es6/Stdlib_Result.js

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -341,6 +341,60 @@ function all6(param) {
341341
}
342342
}
343343

344+
async function mapOkAsync(res, f) {
345+
let value = await res;
346+
if (value.TAG === "Ok") {
347+
return {
348+
TAG: "Ok",
349+
_0: f(value._0)
350+
};
351+
} else {
352+
return {
353+
TAG: "Error",
354+
_0: value._0
355+
};
356+
}
357+
}
358+
359+
async function mapErrorAsync(res, f) {
360+
let value = await res;
361+
if (value.TAG === "Ok") {
362+
return {
363+
TAG: "Ok",
364+
_0: value._0
365+
};
366+
} else {
367+
return {
368+
TAG: "Error",
369+
_0: f(value._0)
370+
};
371+
}
372+
}
373+
374+
async function flatMapOkAsync(res, f) {
375+
let value = await res;
376+
if (value.TAG === "Ok") {
377+
return await f(value._0);
378+
} else {
379+
return {
380+
TAG: "Error",
381+
_0: value._0
382+
};
383+
}
384+
}
385+
386+
async function flatMapErrorAsync(res, f) {
387+
let value = await res;
388+
if (value.TAG === "Ok") {
389+
return {
390+
TAG: "Ok",
391+
_0: value._0
392+
};
393+
} else {
394+
return await f(value._0);
395+
}
396+
}
397+
344398
let getExn = getOrThrow;
345399

346400
let mapWithDefault = mapOr;
@@ -368,5 +422,9 @@ export {
368422
all4,
369423
all5,
370424
all6,
425+
mapOkAsync,
426+
mapErrorAsync,
427+
flatMapOkAsync,
428+
flatMapErrorAsync,
371429
}
372430
/* No side effect */

packages/@rescript/runtime/lib/js/Stdlib_Result.js

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -341,6 +341,60 @@ function all6(param) {
341341
}
342342
}
343343

344+
async function mapOkAsync(res, f) {
345+
let value = await res;
346+
if (value.TAG === "Ok") {
347+
return {
348+
TAG: "Ok",
349+
_0: f(value._0)
350+
};
351+
} else {
352+
return {
353+
TAG: "Error",
354+
_0: value._0
355+
};
356+
}
357+
}
358+
359+
async function mapErrorAsync(res, f) {
360+
let value = await res;
361+
if (value.TAG === "Ok") {
362+
return {
363+
TAG: "Ok",
364+
_0: value._0
365+
};
366+
} else {
367+
return {
368+
TAG: "Error",
369+
_0: f(value._0)
370+
};
371+
}
372+
}
373+
374+
async function flatMapOkAsync(res, f) {
375+
let value = await res;
376+
if (value.TAG === "Ok") {
377+
return await f(value._0);
378+
} else {
379+
return {
380+
TAG: "Error",
381+
_0: value._0
382+
};
383+
}
384+
}
385+
386+
async function flatMapErrorAsync(res, f) {
387+
let value = await res;
388+
if (value.TAG === "Ok") {
389+
return {
390+
TAG: "Ok",
391+
_0: value._0
392+
};
393+
} else {
394+
return await f(value._0);
395+
}
396+
}
397+
344398
let getExn = getOrThrow;
345399

346400
let mapWithDefault = mapOr;
@@ -367,4 +421,8 @@ exports.all3 = all3;
367421
exports.all4 = all4;
368422
exports.all5 = all5;
369423
exports.all6 = all6;
424+
exports.mapOkAsync = mapOkAsync;
425+
exports.mapErrorAsync = mapErrorAsync;
426+
exports.flatMapOkAsync = flatMapOkAsync;
427+
exports.flatMapErrorAsync = flatMapErrorAsync;
370428
/* No side effect */

tests/analysis_tests/tests/src/expected/Completion.res.txt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2613,13 +2613,13 @@ Path g
26132613
"kind": 12,
26142614
"tags": [1],
26152615
"detail": "(result<'a, 'b>, ~message: string=?) => 'a",
2616-
"documentation": {"kind": "markdown", "value": "Deprecated: Use 'getOrThrow' instead\n\n\n `getExn(res, ~message=?)` returns `n` if `res` is `Ok(n)`, otherwise throws an exception with the message provided, or a generic message if no message was provided.\n\n ```res example\n Result.getExn(Result.Ok(42)) == 42\n \n switch Result.getExn(Error(\"Invalid data\")) {\n | exception _ => assert(true)\n | _ => assert(false)\n }\n\n switch Result.getExn(Error(\"Invalid data\"), ~message=\"was Error!\") {\n | exception _ => assert(true) // Throws a JsError with the message \"was Error!\"\n | _ => assert(false)\n }\n ```\n"}
2616+
"documentation": {"kind": "markdown", "value": "Deprecated: Use 'getOrThrow' instead\n\n\n `getExn(res, ~message=?)` returns `n` if `res` is `Ok(n)`, otherwise throws an exception with the message provided, or a generic message if no message was provided.\n\n ```res example\n Result.getExn(Result.Ok(42)) == 42\n \n switch Result.getExn(Error(\"Invalid data\")) {\n | exception _ => true\n | _ => false\n } == true\n\n switch Result.getExn(Error(\"Invalid data\"), ~message=\"was Error!\") {\n | exception _ => true // Throws a JsError with the message \"was Error!\"\n | _ => false\n } == true\n ```\n"}
26172617
}, {
26182618
"label": "Result.getOrThrow",
26192619
"kind": 12,
26202620
"tags": [],
26212621
"detail": "(result<'a, 'b>, ~message: string=?) => 'a",
2622-
"documentation": {"kind": "markdown", "value": "\n `getOrThrow(res, ~message=?)` returns `n` if `res` is `Ok(n)`, otherwise throws an exception with the message provided, or a generic message if no message was provided.\n\n ```res example\n Result.getOrThrow(Result.Ok(42)) == 42\n \n switch Result.getOrThrow(Error(\"Invalid data\")) {\n | exception _ => assert(true)\n | _ => assert(false)\n }\n\n switch Result.getOrThrow(Error(\"Invalid data\"), ~message=\"was Error!\") {\n | exception _ => assert(true) // Throws a JsError with the message \"was Error!\"\n | _ => assert(false)\n }\n ```\n"}
2622+
"documentation": {"kind": "markdown", "value": "\n `getOrThrow(res, ~message=?)` returns `n` if `res` is `Ok(n)`, otherwise throws an exception with the message provided, or a generic message if no message was provided.\n\n ```res example\n Result.getOrThrow(Result.Ok(42)) == 42\n \n switch Result.getOrThrow(Error(\"Invalid data\")) {\n | exception _ => true\n | _ => false\n } == true\n\n switch Result.getOrThrow(Error(\"Invalid data\"), ~message=\"was Error!\") {\n | exception _ => true // Throws a JsError with the message \"was Error!\"\n | _ => false\n } == true\n ```\n"}
26232623
}, {
26242624
"label": "Result.getOr",
26252625
"kind": 12,

0 commit comments

Comments
 (0)