Most popular programming languages use the void
keyword to indicate that a function cannot return a value.
Learning TypeScript describes TypeScript's void
keyword as indicating that the returned value from a function will be ignored.
Those two definitions are not always the same!
TypeScript's void
comes with an initially surprising behavior: a function type with a non-void
return is considered assignable to a function type with a void
return.
ts
letreturnsVoid : () => void;returnsVoid = () => "this is fine";
ts
letreturnsVoid : () => void;returnsVoid = () => "this is fine";
Why is that?
void
Is More of an Idea
It's very common for functions to be called and have their return type ignored.
Take this code block, in which a function that returns number
(because Array's .unshift
returns the new array length) is passed to .forEach
:
ts
letletters = ["c", "d", "e"];["a", "b"].forEach ((letter ) =>letters .unshift (letter )); // Ok
ts
letletters = ["c", "d", "e"];["a", "b"].forEach ((letter ) =>letters .unshift (letter )); // Ok
We should be allowed to write that code in TypeScript!
.forEach
's function parameter's return type is correctly marked as void
because the value returned from the function is ignored.
Functions that return a value can be used in locations that don't care about their returned value.
void
is not any
Note that TypeScript will not allow you to return a value within a function explicitly annotated as returning void
.
TypeScript will report an assignability type error:
ts
functionthisIsNotFine (): void {return "🔥";Type 'string' is not assignable to type 'void'.2322Type 'string' is not assignable to type 'void'.// Error: Type 'string' is not assignable to type 'void'.}
ts
functionthisIsNotFine (): void {return "🔥";Type 'string' is not assignable to type 'void'.2322Type 'string' is not assignable to type 'void'.// Error: Type 'string' is not assignable to type 'void'.}
void
is not a free pass to return whatever you want.
It's an explicit indication that no return value is meant to be used.
Addendum: Misused Promises
Maybe I'm missing something, but assigning functions that return non-void values to a void-returning function sounds like an opening for bugs to me…
Indeed, it is! Passing an asynchronous function (one that returns a Promise) to a handler that doesn't handle the created Promise means Promise rejections might not be handled. This frequently shows up with event listeners in UI libraries such as React:
tsx
async functionmyOnClick () {// (simulate some asynchronous work happening)await newPromise ((resolve ) =>setTimeout (resolve , 100));// (simulate some crashing bug in the code)if (Math .random () > 0.5) {throw newError ("Oh no!");}}// The "Oh no!" Error won't be logged anywhere or handled...constmyButton = <button onClick ={myOnClick }>...</button >;
tsx
async functionmyOnClick () {// (simulate some asynchronous work happening)await newPromise ((resolve ) =>setTimeout (resolve , 100));// (simulate some crashing bug in the code)if (Math .random () > 0.5) {throw newError ("Oh no!");}}// The "Oh no!" Error won't be logged anywhere or handled...constmyButton = <button onClick ={myOnClick }>...</button >;
TypeScript ESLint's no-misused-promises rule can catch cases like this automatically for you. The no-floating-promises rule is also useful for catching other classes of "floating" Promises.
This article was inspired by the thread on this tweet: https://twitter.com/haroenv/status/1534186668244230155
Got your own TypeScript questions? Tweet @LearningTSBook and the answer might become an article too!