Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Can I write a type guard that throws exceptions instead of returning booleans?

I have a class that uses the same type guard in multiple functions; something like this:

function validData(d: Data | null): d is Data {
    return d !== null;
}

class C {
    data: Data | null;

    public doA() {
        if (!validData(this.data))
            throw new Error("Invalid data");
        /* … */
    }

    public doB() {
        if (!validData(this.data))
            throw new Error("Invalid data");
        /* … */
    }
}

Can I refactor this code to move the error into the type guard? Something like this:

function assertData(d: Data | null): ??? {
    if (d === null)
        throw new Error("Invalid data");
}

…which I could use like this:

class C {
    data: Data | null;

    public doA() {
        assertData(this.data);
        /* … */
    }

    public doB() {
        assertData(this.data);
        /* … */
    }
}

Currently I'm using the following workaround:

function must(d: Data | null): Data {
    if (d === null)
        throw new Error("Invalid data");
    return d;
}

… but this forces me to wrap every access to this.data in must().

like image 777
Clément Avatar asked Apr 06 '18 05:04

Clément


People also ask

What is a type guard function?

A type guard is a TypeScript technique used to get information about the type of a variable, usually within a conditional block. Type guards are regular functions that return a boolean, taking a type and telling TypeScript if it can be narrowed down to something more specific.

How do I return a TypeScript error?

To declare a function that throws an error, set its return type to never . The never type is used for functions that never return a value, in other words functions that throw an exception or terminate execution of the program. Copied! The never type is used very rarely in TypeScript.


1 Answers

Edit Since the original answer, typescript has added the ability for custom type assertions in this PR

type Data = { foo: string };

function assertData(d: Data | null): asserts d is Data {
    if (d == null)
        throw new Error("Invalid data");
}
// Use
declare var bar: Data | null;
bar.foo // error as expected
assertData(bar)
bar.foo // inferred to be Data

Playground Link

Original answer

Unfortunately the current syntax for type guards requires an if statement for them to work. So this works

type Data = { foo: string };
function assertData(d: Data | null): d is Data {
    if (d == null)
        throw new Error("Invalid data");
    return true;
}
// Use
let bar: Data | null = null;
if (assertData(bar)) {
    bar.foo // inferred to be Data
}

But there is no way to get this to work:

let bar: Data | null = null;
assertData(bar);
bar.foo // bar will still be Data | null
like image 58
Titian Cernicova-Dragomir Avatar answered Sep 19 '22 21:09

Titian Cernicova-Dragomir