In my Typescript project I have a Result type that I return from functions, containing either an error or some data. It can be either of the form [Error, null]
, or [null, Data]
. For example:
type Result<Data> = [ Error | null, Data | null ]
function randomSuccess(): Result<string> {
if ( Math.random() > 0.5 ) {
return [ null, 'success' ]
else {
return [ new Error( 'oh no' ), null ]
}
}
const [ err, result ] = randomSuccess()
if ( err ) {
.... // can now handle the extracted error and result
I would like Typescript to check that only one of the Error or Data is ever not null. For example:
...
return [ new Error( 'oh no' ), 'success' ]
should throw an error.
My initial attempt at writing a type for this was with conditional types:
type Result<Data> = null extends Error ? [ null, Data ] : [ Error, null ]
This compiles fine when the function returns an error. However when returning valid data - for example return [ null, 'success' ]
- the compiler complains:
Type 'null' is not assignable to type 'Error'
I think I understand the compiler error: in my type definition Error
is not a parameter so null extends Error
will always be false. However I don't know where to go from here.
How can I make a type that is either [Error, null]
, or [null, Data]
, and never [Error, Data]
?
You're looking for a union type where the parts of the union are the two tuples (arrays) that represent the two possible states:
type Result<Data> =
[ null, Data ] // Success
|
[ Error, null ]; // Failure
A Result<Data>
can either have one form or the other, but can't have null
in both places, and can't have Error, Data
. (Also can't have a length other than 2.)
Here's an example:
type Result<Data> =
[ null, Data ] // Success
|
[ Error, null ]; // Failure
function random(): Result<Date> {
// ^^^^−−−− Using `Date` for the `Data` type argument
// just for example
if (Math.random() < 0.5) {
// Fail
return [new Error(), null];
}
// Succeed
return [null, new Date()];
}
const a = random();
if (a[0]) {
// TypeScript now knows that `a[0]` is an `Error`
console.log(`Error: ${a[0].message}`);
} else {
// TypeScript now knows that `a[1]` is a `Date`
console.log(`Success at ${a[1].toString()}`);
}