Instantiation
Minimize Direct Instantiation API Surface
The proposal currently offers four ways to instantiate results: two direct and two indirect.
-
Static factory methods (direct):
const okResult = Result.ok(value);
const errResult = Result.error(error);
-
Constructor (direct):
const okResult = new Result(true, undefined, value);
const errResult = new Result(false, error);
-
Static try method on a non-function/non-promise (indirect):
const okResult = Result.try(value);
-
try operator on a non-function/non-promise (indirect):
const okResult = try value;
Indirect ways can be flagged by linters as suboptimal. The preferred approach is the static factory methods (Result.ok, Result.error), which are concise and less error-prone.
The constructor approach introduces problems, especially for JS (non-TS) developers:
-
value and error can be swapped, creating incorrect results.
-
Using two arguments for errors encourages incorrect use for success cases.
-
Ambiguity in specifying ok, error, and value:
ok: true/false, 1/0, "success"/"" etc.
error in ok-results: null, undefined, any ignored value.
value in error-results: same ambiguity.
-
Non-intuitive behavior when both error and value are set.
Explicit new Result(...) must throw an error:
const result = new Result(...); // throws
const result = new (Result.ok()).constructor(...); // throws
Void Results
Factory methods must allow creating results without values:
const voidOk = Result.ok();
const voidErr = Result.error();
Useful when details don’t matter.
Instance Check
Both checks must return true:
Result.ok(..) instanceof Result
Result.error(..) instanceof Result
Properties
Result instances have three properties:
ok
error (only if ok is false)
value (only if ok is true)
Properties must be readonly. This prevents inconsistent states, e.g., ok true but with an error, or vice versa. Instances should also be sealed to prevent adding error to ok-results or value to error-results. This is especially important if discriminating with 'error' in result or 'value' in result is supported.
Instance Methods
Algebraic Data Types
Result models success/failure flows similar to other languages, supporting:
- Functor
- Monad
- Bi-Functor
- Bi-Monad
Required methods (like in Array):
class Result {
map(mapValueFn, mapErrorFn?) {}
mapError(mapErrorFn) {}
flatMap(chainValueFn, chainErrorFn?) {}
flatMapError(chainErrorFn) {}
flat() {}
}
Optional combined naming (like in Promise):
class Result {
andThen(handleValue, handleError?) {}
orElse(handleError) {}
}
Unwrapping Methods
Direct field access is preferred, but unwrapping is useful, especially for TS developers:
const value = result.unwrap(); // returns value or throws TypeError with cause=result
Unwrap methods:
class Result {
unwrap(options?) {}
unwrapError(options?) {}
}
options can:
A match method is also recommended:
class Result {
match(onOk, onError) {}
}
Example:
app.post('/some-resource', (req, res) => {
res.json(
someService.doSomething().match(
value => ({ status: 'success', data: value }),
error => ({ status: 'error', error: error.message }),
),
);
});
Static Methods
Currently proposed static methods:
Additional methods for arrays/tuples of results:
- collect – returns an ok result with an array of values if all are ok, or the first non-ok result.
- collectRight – same as collect, but returns the last non-ok result instead of the first.
Implementation
It is recommended to implement two separate classes instead of one generic Result:
- OkResult – represents a successful result.
- ErrorResult – represents an error result.
This simplifies method implementations by removing the need for branching logic inside every method. Each subclass can implement its own version of map, flatMap, unwrap, etc., without runtime checks. Factory methods (Result.ok, Result.error) return instances of these classes, but they share a common base class Result to ensure instanceof Result works consistently.
Instantiation
Minimize Direct Instantiation API Surface
The proposal currently offers four ways to instantiate results: two direct and two indirect.
Static factory methods (direct):
Constructor (direct):
Static try method on a non-function/non-promise (indirect):
tryoperator on a non-function/non-promise (indirect):Indirect ways can be flagged by linters as suboptimal. The preferred approach is the static factory methods (Result.ok, Result.error), which are concise and less error-prone.
The constructor approach introduces problems, especially for JS (non-TS) developers:
valueanderrorcan be swapped, creating incorrect results.Using two arguments for errors encourages incorrect use for success cases.
Ambiguity in specifying
ok,error, andvalue:ok: true/false, 1/0, "success"/"" etc.errorin ok-results: null, undefined, any ignored value.valuein error-results: same ambiguity.Non-intuitive behavior when both
errorandvalueare set.Explicit
new Result(...)must throw an error:Void Results
Factory methods must allow creating results without values:
Useful when details don’t matter.
Instance Check
Both checks must return true:
Properties
Result instances have three properties:
okerror(only ifokis false)value(only ifokis true)Properties must be readonly. This prevents inconsistent states, e.g.,
oktrue but with an error, or vice versa. Instances should also be sealed to prevent addingerrorto ok-results orvalueto error-results. This is especially important if discriminating with'error' in resultor'value' in resultis supported.Instance Methods
Algebraic Data Types
Result models success/failure flows similar to other languages, supporting:
Required methods (like in Array):
Optional combined naming (like in Promise):
Unwrapping Methods
Direct field access is preferred, but unwrapping is useful, especially for TS developers:
Unwrap methods:
optionscan:Provide a default:
Provide an expected error message:
Handle error explicitly:
A
matchmethod is also recommended:Example:
Static Methods
Currently proposed static methods:
okerrortryAdditional methods for arrays/tuples of results:
Implementation
It is recommended to implement two separate classes instead of one generic
Result:This simplifies method implementations by removing the need for branching logic inside every method. Each subclass can implement its own version of
map,flatMap,unwrap, etc., without runtime checks. Factory methods (Result.ok,Result.error) return instances of these classes, but they share a common base classResultto ensureinstanceof Resultworks consistently.