Is there a function that provides a value based on an assertion?

I have been working on creating a function with the following structure:

function validate<T>(input: T, message?: string): T & asserts input {
  if (!input) {
    throw new Error(message)
  }
  return input
}

The main aim of this function is to return the unaltered input while also indicating to the compiler that the returned value is truthy. This enables it to be used as a standalone statement or within an expression seamlessly.

const result: boolean = true;
// ... code segment that might change the value of 'result' to false
validate(result);

const element: HTMLElement = validate(document.getElementById('foo'));

Regrettably, the syntax presented above does not function as expected (playground). Is there a way to achieve this functionality in TypeScript?

I have searched extensively for official documentation on assertion functions outside of the TypeScript 3.7 Release notes, which only showcase assertion functions that implicitly return void.

The closest solution I could devise is a workaround applicable solely to object types, as they can never be falsy:

function validate<T extends object>(input: T | null | undefined, message?: string): T {
  if (!input) {
    throw new Error(message)
  }
  return input
}

Answer №1

Q: Can assertion functions return a value?

A: No, they cannot

Finding information about this topic can be challenging as it is not clearly documented. However, authoritative sources confirm that assertion functions implicitly return void, making it impossible for them to return any other value.

Authoritative Sources

Typescript Github: PR #32695 Assertions in control flow analysis

The asserts return type predicate dictates that the returned value must be of type void, ruling out the possibility of returning values of other types.

https://github.com/ahejlsberg Anders Hejlsberg

(This clarification comes from Anders Hejlsberg himself, the creator of the codebase)

Typescript issue tracker: #34636 ReturnType support for assertions

This design choice was deliberate, and the conditions under which an assert function call is recognized emphasize that its return is always treated as void.

https://github.com/dragomirtitian Titian Cernicova-Dragomir

(Titian has been a valuable contributor to the Typescript project)

Community Opinions

Various community sources also echo the sentiment that assertion functions do not allow specifying return types.

dev.to - Typescript Type Assertions)

While type guards must return a boolean value, assertion functions are bound to return void.

https://dev.to/smeijer Stephan Meijer

2ality.com - TypeScript: narrowing types via type guards and assertion functions - 5.2.2

Asserting a variable's type guarantees that it is only void or throws an exception.

Axel Rauschmayer

Official Documentation

Regrettably, there is no definitive mention of this limitation in the official documentation...

Handbook: Narrowing

The handbook touches upon type assertions but omits details concerning assertion functions specifically.

Cheat sheet: Control Flow Analysis

Mentions assertion functions without indicating a way to specify return types, leaving ambiguity on whether they can have one.

v3.7 Release Notes: Assertion functions

No explicit mentions or examples are provided regarding assertion functions that return values, hinting at an implicit behavior of always returning void.

Typescript Playground: Assertion functions

Although no illustration of assertion functions returning values is given here, the absence of clear statements implies inconclusiveness on the subject.

Answer №2

In the world of Javascript, there exists a definite group of values known as "falsy". Some of these values cannot be directly written as literal types in Typescript, while others include 0, '', false, null, and undefined. To filter out these falsy values from the type parameter T, you can utilize the Exclude method:

type Truthy<T> = Exclude<T, 0 | '' | false | null | undefined>

function checkTruthy<T>(x: T): Truthy<T> {
    // ...
}

Another option is to use an assertion function:

function assertTruthy<T>(x: T): asserts x is Truthy<T> {
    // ...
}

However, attempting to write a function type annotation that combines both approaches will result in an error message. Yet, in scenarios where the assertion function simply returns the argument as is, you can easily refactor the call to avoid this issue:

// instead of this
let y = assertTruthy(x) + 'foo';

// do this
assertTruthy(x);
let y = x + 'foo';

It's important to note that Typescript lacks the ability to represent types like "a non-empty string" or "a non-zero number". Therefore, using Truthy<string> yields no advantage over string regardless of whether you return or assert. The real benefit of utilizing Exclude comes into play when dealing with unions of literals such as 'foo' | 'bar' | ''.

Answer №3

If you want to ensure non-nullable types in TypeScript, you can make use of the NonNullable<T> utility type:

function ensureNonNullable<T>(value: T, errorMessage?: string): NonNullable<T> {
  return value ?? throwError(errorMessage);
}

// workaround for handling issues like https://github.com/microsoft/TypeScript/issues/18535
function throwError(errorMessage?: string): never {
  throw new Error(errorMessage);
}

const nullableString: string | null = null;
const nonNullableString: string = ensureNonNullable(nullableString);

Explore this code further in the TypeScript Playground

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

I cannot access the 'isLoading' state in React Query because it is undefined

Since updating to the latest version of react query, I've been encountering an issue where the 'isLoading' state is returning undefined when using useMutation. Here's the code snippet in question: const useAddUserNote = (owner: string) ...

Ensure that a string contains only one instance of a specific substring

I need a function that removes all instances of a specific substring from a string, except for the first one. For example: function keepFirst(str, substr) { ... } keepFirst("This $ is some text $.", "$"); The expected result should be: This $ is some tex ...

Having trouble writing Jest test cases for a function that returns an Observable Axios response in Nest JS

I'm relatively new to the NestJS + Typescript + RxJs tech stack and I'm attempting to write a unit test case using Jest for one of my functions. However, I'm uncertain if I'm doing it correctly. component.service.ts public fetchCompon ...

Incorporate axios within getStaticProps while utilizing Next.js

I am working on a new project where I am utilizing axios to handle request data. However, I am facing an issue when using axios in the getStaticProps function which results in an error on my index.js page. Here is a snippet of my index.js code: import ...

How does Typescript overlook such a peculiar inconsistency?

I've come across a peculiar situation where Typescript doesn't seem to differentiate between an object like {} and a generic array []. It accepts the latter as input for a function that is supposed to require an object with {}'s structure. ...

What steps can be taken to resolve the error message "Module '../home/featuredRooms' cannot be found, or its corresponding type declarations"?

Upon deploying my site to Netlify or Vercel, I encountered a strange error. The project runs smoothly on my computer but seems to have issues when deployed. I am using TypeScript with Next.js and even attempted renaming folders to lowercase. Feel free to ...

In my current project, I am working with Knockout and TypeScript but I am encountering difficulties in firing the window-resize event

Instead of using jquery, I prefer working with a custom handler for the $(window).resize(function () { ... event. If there is a way to achieve this without relying on jquery, please feel free to share it in the comments below. The code snippet below show ...

Unable to perform navigation during page load in a React.js application

I attempted to navigate to a route that should redirect the user back to the homepage when postOperations isn't set in the localStorage. To save time, please review the code snippet focusing on the useEffect and the first component inside return(). im ...

Retrieving Vue component properties as a data type

I'm facing a dilemma with my Vue components. I want to extract the props from one component and use them as a type instead of a value in another component. Specifically, I have a component where I need to take in an array of props from a different com ...

Issues arising from an aging Angular project

I'm currently facing an issue with installing node and typescript for an older angular project that I have inherited. This project is using angular 2.4.10 After downloading and installing node version 6.9.5 from the executable file, I proceeded to in ...

Ensuring there are no null values in TypeScript

I am encountering an issue with the following TypeScript code: console.log ('collection[0] -> ' + collection[0] ); console.log ('collection[0] !== null -> ' + collection[0] !== null); However, the output on the console is unexp ...

Mastering VSCode IntelliSense: Unleashing the Power of Type Declarations

In my JavaScript projects, I aim to include TypeScript types sparingly to leverage IntelliSense for better code completions and receive warnings about type-related issues. To set up typechecking in JS, I created a jsconfig.json file and rely mostly on JSD ...

Error: Unable to locate Angular2 Custom Service

I have implemented a custom service to populate a list of people in my HTML. Below is the code for my custom service: app.peopleListService.ts import { Injectable } from '@angular/core'; import { Person } from "../model/peopleModel"; @Injecta ...

Issue with MongoDB $push within an Array of Arrays: The shorthand property 'elements' does not have a value available in scope

I am encountering an issue while trying to insert data into Elements in MongoDB using TypeScript. Any suggestions on how to resolve this problem? Attempt 1 Error: I am receiving an error message stating "No value exists in scope for the shorthand property ...

Struggling to properly import the debounce function in ReactJS when using TypeScript

I am facing an issue while trying to properly import the debounce library into my react + typescript project. Here are the steps I have taken: npm install debounce --save typings install dt~debounce --save --global In my file, I import debounce as: impo ...

How to set up an Angular ErrorHandler?

Attempted to register an Angular ErrorHandler in this simplified StackBlitz environment, but it seems that it's not capturing the error thrown within the constructor of the HelloComponent. Any insights on this issue? Your opinions? ...

Issue: ASSERTION ERROR: token must be declared [Expecting => null is not undefined <=Actual]

I encountered an error while working on my project. The only special thing I did was use oidc(openId) for authentication. I made some changes to the bootstrap project and now the first component that is running is the home-main component, which includes t ...

Why bother with creating mappers to transform entity-to-DTOs?

There are classes referred to as 'mappers' that are utilized by some individuals for converting DTOs to entities or vice versa. What benefits do I stand to gain from employing this technique during backend development? I am keen on delving deepe ...

My approach to retrieving data from Firebase and converting it into an array of a specific type

Recently, I made the decision to expand my iOS application to also function as a web app. Although the implementation seems to be working, I am unsure if it is done correctly. I would appreciate it if someone could confirm if the implementation is correct. ...

How can you incorporate a module for typings without including it in the final webpack bundle?

As I venture into the realm of Webpack, I am faced with the challenge of transitioning from TypeScript 1.x to TypeScript 2. In my previous projects, I typically worked with TypeScript in one module using separate files, TSD for typings, and compiling throu ...