top tip:
Throwing an error as a placeholder in typescript development

Aug 10, 2020

TLDR: If a function throws an error, typescript won't complain about it not returning what it is meant to return

Note - this post uses typescript 3.9.7 - error messages may vary in older versions.

While working on the same project as in my post about console.log, I came upon a small frustration.

function noReturn(): number {}

rightly throws the error error TS2355: A function whose declared type is either 'void' nor 'any' must return a value.

Most of the time this is useful, but when you're starting a project, or in my case, porting a complicated inherited class, you might want to just give all the methods types before implementing them.

It's also more useful in the case where it shows you when some particular logic path doesn't return the type it should, like this contrived example,

function onePathReturns(willReturn: boolean): number {
if (willReturn) return 5;
}

which throws error TS2366: Function lacks ending return statement and return type does not include 'undefined'.

These errors can be frustrating, though, because your code will not compile. And if your tests are also written in typescript (this project uses ts-jest), the tests won't even compile, meaning you can't see whether they are passing or not.

As this project had a lot of calculations, a lot of these methods would return numbers. So, I tried just returning a constant value. But weeks later, I found that a method I'd written was broken because another method I was calling was always returning 8, I found:

method(argument: number): number {
return 8; // TODO implement
}

which I found funny. After posting a screenshot to the team chat, I switched to returning NaN.

But the problem there, was that you can get NaN through errors of your own. Also, this particular project returns NaN on purpose in some places. So you run the risk of the always returns 8 problem recurring.

Then I tried:

method(argument: number): number {
throw new Error('Not Yet Implemented!');
}

and... it compiled! Rather helpfully, the typescript compiler knows that it doesn't matter that you don't have a return statement, because it'll never be reached.

Not only that, but when you try to call it, in my case by running ts-jest, you get a nice test fail, telling you that it is failing because you haven't implemented it yet, with a stack trace that tells you exactly where the problem is.

You can also use this to close off a certain logic branch that isn't implemented yet, while you work on the other case:

function oneBranchReturnsOneBranchThrows(willReturn: boolean): number {
if (willReturn) return 5;
throw new Error('not yet implemented');
}
// Compiles!

which was a great help in my project.

Even better, wherever else in your code you call method, the compiler will assign the output its correct type - so in this case, the return type of oneBranchReturnsOneBranchThrows will be treated as a number and all will be well. (I mean, we can't actually run the code because it throws an error, of course, but it works as a placeholder).

Side note: I haven't used them yet, but it looks like @ts-expect-error comments might cover some of the same ground. But they seem like they serve a slightly different purpose - more like something you leave permanently in your code, to allow runtime JS checks without the compiler complaining, rather than a placeholder to let you keep developing at a decent pace.