Is it possible to compose a type which represents a tuple that returns either an error or the value?
async function createUser(userData): Promise<Either<User, UserError>> {}
// This should return a tuple where we have either an user or an error. Not both, not none
const [ user, userError ] = await createUser({ ... })
// Checking if there is an error
if (userError) {
...
// This line represents an early bail, could be throw or return
throw userError
}
// Since we check the error above, this should now be safe to use
console.log(user.name)
CodePudding user response:
You can create such a type like this:
type Either<A, B> = [A, undefined?] | [undefined, B];
And it will be handled by TypeScript's control flow analysis the way you want it to:
type Either<A, B> = [A, undefined?] | [undefined, B];
type User = { name: string };
class UserError extends Error {
override name = 'UserError';
}
declare function createUser(userData: User): Promise<Either<User, UserError>>;
const [ user, userError ] = await createUser({name: 'foo'});
user; // User | undefined
userError; // UserError | undefined
if (userError) {
userError; // UserError
throw userError;
}
user; // User
user.name; // string
However, I prefer using a union to avoid the extra tuple and variable (inspired by Rust's Result
type):
type Result <T = void, E extends Error = Error> = T | E;
function isError <T>(value: T): value is T & Error {
return value instanceof Error;
}
type User = { name: string };
class UserError extends Error {
override name = 'UserError';
}
declare function createUser(userData: User): Promise<Result<User, UserError>>;
const user = await createUser({name: 'foo'});
user; // User | UserError
if (isError(user)) {
user; // UserError
// ... do something else
throw user;
}
user; // User
user.name; // string