When the callback passed to mapOk throws synchronously, the error escapes as an unhandled rejection. The resulting Future never resolves, making it impossible for any downstream consumer to handle the error.
Reproduction
import { Future, Result } from "@swan-io/boxed";
process.on("unhandledRejection", (e) =>
console.log("unhandledRejection:", e),
);
const future = Future.fromPromise(Promise.resolve({ name: null }))
.mapOk((data) => data.name.toUpperCase()); // throws TypeError
// None of these ever fire:
future.tap((result) => console.log("tap:", result));
future.toPromise().then(console.log).catch(console.log);
future.resultToPromise().then(console.log).catch(console.log);
// Output: unhandledRejection: TypeError: Cannot read properties of null (reading 'toUpperCase')
// → tap, toPromise, resultToPromise never settle
What happens
When the callback passed to mapOk throws, the exception escapes the Future abstraction. The error becomes an unhandled Promise rejection and the derived Future never settles, leaving downstream consumers hanging.
Expected behavior
The throw should be caught and the Future should resolve with Result.Error(thrownError), so that .mapError, .tap, .resultToPromise etc. can handle it normally.
Workaround
Using mapOkToResult + Result.fromExecution works:
Future.fromPromise(Promise.resolve({ name: null }))
.mapOkToResult((data) =>
Result.fromExecution(() => data.name.toUpperCase()),
)
.tap(console.log);
// → Result.Error(TypeError: Cannot read properties of null ...)
Suggested fix
Make mapOk internally delegate to mapOkToResult + Result.fromExecution:
mapOk(func) {
return this.mapOkToResult((value) =>
Result.fromExecution(() => func(value))
);
}
Same issue likely applies to map, flatMapOk, and other methods that invoke user callbacks during resolution.
Note: since JavaScript allows throwing arbitrary values, this would result in Result<_, unknown>, consistent with Result.fromExecution.
Context
We hit this in a NestJS/Fastify backend. A corrupted database record caused mapOk to throw during entity mapping. The HTTP request hung indefinitely because the Future never settled and no response was sent. The only trace was a logged unhandled rejection.
Version: 3.2.1
When the callback passed to
mapOkthrows synchronously, the error escapes as an unhandled rejection. The resulting Future never resolves, making it impossible for any downstream consumer to handle the error.Reproduction
What happens
When the callback passed to mapOk throws, the exception escapes the Future abstraction. The error becomes an unhandled Promise rejection and the derived Future never settles, leaving downstream consumers hanging.
Expected behavior
The throw should be caught and the Future should resolve with
Result.Error(thrownError), so that.mapError,.tap,.resultToPromiseetc. can handle it normally.Workaround
Using
mapOkToResult+Result.fromExecutionworks:Suggested fix
Make
mapOkinternally delegate tomapOkToResult+Result.fromExecution:Same issue likely applies to
map,flatMapOk, and other methods that invoke user callbacks during resolution.Note: since JavaScript allows throwing arbitrary values, this would result in Result<_, unknown>, consistent with Result.fromExecution.
Context
We hit this in a NestJS/Fastify backend. A corrupted database record caused
mapOkto throw during entity mapping. The HTTP request hung indefinitely because the Future never settled and no response was sent. The only trace was a logged unhandled rejection.Version: 3.2.1