Paul Sachs
Paul Sachs

Reputation: 1025

Typescript: Type narrowing on promises

I'm trying to figure out how to use typeguards on promises based on parameters.

function request({ logic }: { logic: boolean }) {
    return new Promise((resolve, reject) => {
        if (l)
            resolve("something");
        resolve(1);
    });
}

request({ logic: true }).then(a => {
    a.length
})

In this example, I'd like to make sure that the typeof 'a' == 'string'. I tried writing some typeguards in request but their results get lost. I don't know if this is just a limitation of typescript or I just need to do some smart type casting or what.

This is a toy example of what I am actually trying to do, which is to make an async call whose result varies slightly based on some parameters. And I am loath to make another function just to cover an altered return type

Upvotes: 2

Views: 758

Answers (2)

Tamas Hegedus
Tamas Hegedus

Reputation: 29906

Typescript function overloading to the rescue:

function request(logic: true): Promise<string>;
function request(logic: false): Promise<number>;
function request(logic: boolean) {
    return new Promise((resolve, reject) => {
        if (logic) 
            resolve("something");
        resolve(1);
    });
}

request(true).then(a => {
    console.log(a.length); //<-- knows that a is a string
});

request(false).then(a => {
    console.log(a.length); //<-- error: prop 'length' does not exist on number
});

Typeguards are meant to be used in if statements.

EDIT

You would be surprised! Typescript supports overloading distinction based on fields too! Check the following code:

function request(opts: { logic: true }): Promise<string>;
function request(opts: { logic: false }): Promise<number>;
function request(opts: { logic: boolean }) {
    return new Promise((resolve, reject) => {
        if (opts.logic) 
            resolve("something");
        resolve(1);
    });
}


request({ logic: true }).then(a => {
    console.log(a.length); //<-- knows that a is a string
});

request({ logic: false }).then(a => {
    console.log(a.length); //<-- error: prop length cannot be found on type number
});

EDIT

With a little generic magic you can achieve the desired behavior. This way only the logic field matters from the caller's point of view. Downside is that you loose typecheck even for opts.logic inside the request functions implementation.

function request<T extends { logic: true }>(opts: T): Promise<string>;
function request<T extends { logic: false }>(opts: T): Promise<number>;
function request(opts: any) {
    return new Promise((resolve, reject) => {
        if (opts.logic) 
            resolve("something");
        resolve(1);
        console.log(opts.anything);
    });
}

request({ logic: true, foo: 'bar' }).then(a => {
    console.log(a.length); //<-- knows that a is a string
});

request({ logic: false, foo: 'baz' }).then(a => {
    console.log(a.length); //<-- error: prop length cannot be found on type number
});

Upvotes: 3

Staxaaaa
Staxaaaa

Reputation: 154

Correct overloading is the next (should add type):

function request(logic: boolean): Promise<string>;
function request(logic: boolean): Promise<number>;
function request(logic: boolean): Promise<any>;
function request(logic: boolean) {
    return new Promise((resolve, reject) => {
        if (logic) 
            resolve("something");
        resolve(1);
    });
}

request(true).then((a) => {
    console.log(a.length); //<-- knows that a is a string
});

request(false).then((a) => {
    console.log(a.length); //<-- error: prop 'length' does not exist on number
});

Upvotes: -1

Related Questions