DN26
DN26

Reputation: 82

How do I create tuple types that depend on each other in TypeScript?

I have the following function that allows me to handle async errors similar to Go.

type AsyncResults<ErrorType, DataType> = Promise<
    readonly [undefined, DataType] | readonly [ErrorType, undefined]
>;

const asyncHandler = <ErrorType, DataType>(
    asyncFunc: Promise<DataType>,
): AsyncResults<ErrorType, DataType> => {
    return asyncFunc
        .then((data: DataType) => [undefined, data] as const)
        .catch((error: ErrorType) =>
            Promise.resolve([error, undefined] as const),
        );
};

When I call it, I usually get error type to be 'unknown', which is ok, but my result type is always whatever is inferred union undefined

const [err, verifiedUser] = await asyncHandler(
        getPasswordVerifiedUser(db, {
            email,
            password,
        }),
    );
if (err) throw err
const user: VerifiedUser = {
   firstName: verifiedUser.firstName (TS complains that object may be undefined)
}

So verifiedUser above has inferred type 'Document | undefined'. Is there a way for TypeScript to know that if err is undefined then verifiedUser cannot be undefined (and vice versa)? I want that if I handle the err by throwing or returning, TS recognizes that verifiedUser can no longer be undefined.

Upvotes: 2

Views: 212

Answers (1)

Guerric P
Guerric P

Reputation: 31815

What you are trying to do is impossible. TypeScript does not make any difference between a resolution and a rejection. This is how reject and resolve are being typed:

reject<T = never>(reason?: any): Promise<T>;
resolve<T>(value: T | PromiseLike<T>): Promise<T>;

They both return Promise<T> so there is no possible way to narrow down a Promise's return type based on the control-flow.

More information about this here: https://github.com/Microsoft/TypeScript/issues/7588#issuecomment-199079907

Edit:

If you want to narrow the tuple type union, just don't destructure it:

// Considering ErrorType is number and DataType is { firstName: string }

const result = await asyncHandler<number, { firstName: string }>(
    getPasswordVerifiedUser<{ firstName: string }>(),
);

if (result[0] !== undefined) throw result[0]

const user = {
    firstName: result[1].firstName // works
}

It's the only solution that I found and it seems to be confirmed here: Control flow not type-narrowing with union tuple?

Upvotes: 1

Related Questions