Tips for incorporating null checks into a function in TypeScript

In my code, I have multiple functions that require a null check on an object before proceeding with certain actions or throwing an error if the object is null. Here is an example of what it looks like:

interface SomeObject {
    doThingOne: () => string;
    doThingTwo: () => string;
}

let object: SomeObject | null; 

function doThingOne() {
  if(!object) {
    throw new Error('Object is null!');
  }
  return object.doThingOne();
}

function doThingTwo() {
  if(!object) {
    throw new Error('Object is null!');
  }
  return object.doThingTwo();
}

I want to refactor this code by extracting the null check into a separate function to avoid duplication. This is how I attempted to do it:

interface SomeObject {
    doThingOne: () => string;
    doThingTwo: () => string;
}

let object: SomeObject | null; 

function requireObject() {
  if(!object) {
    throw new Error('Object is null!');
  }
}

function doThingOne() {
  requireObject();
  return object.doThingOne();
}

function doThingTwo() {
  requireObject();
  return object.doThingTwo();
}

However, after refactoring the code, TypeScript no longer recognizes that object exists after the null check. Is there a more efficient way to achieve this without losing type inference?

This question on Stack Overflow appears similar, but it doesn't provide a solution that significantly reduces code repetition as I would still need an if statement.

Answer №1

There is no direct way to manipulate the type of object by simply calling requireObject(), as the compiler does not establish a connection between the two. Handling all potential state changes resulting from mutations within functions with closed-over variables is beyond the computational capacity of the compiler. You can refer to microsoft/TypeScript#9998 for more details on this challenge.

One approach to achieve this is by passing object as a parameter to requireObject() and indicating that requireObject() is an assertion function. Assertion functions need to be implemented with a return type of void (signifying that they don't return anything), and the annotated return type should take the form of an "assertion predicate" like asserts x is Y (where x represents one of the function parameters) or simply asserts x to denote that x is truthy.

An example implementation looks like this:

function requireObject(x: any): asserts x {
  if (!x) {
    throw new Error('something is falsy here');
  }
}

With this in place, we can safely execute operations without encountering errors, as shown below:

let object: SomeObject | null;

function doThingOne() {
  requireObject(object);
  return object.doThingOne(); // no issues
}

function doThingTwo() {
  requireObject(object);
  return object.doThingTwo(); // no issues
}

Link to code in Playground

Answer №2

To ensure that the object variable will always have a value, you can utilize the ! operator when working with TypeScript.

return object!.doThingOne();

I personally prefer to steer clear of this approach as it can lead to potential issues down the road. Instead, I recommend creating a function called requireObject which guarantees the presence of an object.

function requireObject(): object {
    if (!object) {
        throw new Error('Object is null!');
    }
    return object;
}

You can then invoke this function like so:

object = requireObject();

By always returning an object type in the requireObject function, the compiler will be confident that the object variable is not null at that specific instance.

The only downside now is that because object is a TypeScript type, the code might be slightly confusing. Although it should function correctly (though untested), renaming the variable could enhance clarity.

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

How do you type a function in TypeScript that accepts an object with properties of any other type, including none at all?

My preference in animals type Animal = { name: string; age: number; dimensions: number[]; }; Now I aim to create a function that allows me to input an object with any combination of those fields, but strictly only those fields and with the correct t ...

After the assignment, TypeScript reordered the elements of the array

Dealing with an array of objects for use in a ngFor loop has presented a challenge. The issue arises when the order that was initially set for the array changes unexpectedly due to JavaScript manipulation. Originally, the array is ordered as expected when ...

Tips for sending data from a child component to a parent component

I am facing an issue with my components setup. I have 3 components - 2 child components and 1 parent component. The parent component has a save button, and in order to get data from the child components in the parent component, I have implemented the follo ...

When evaluating objects or arrays of objects to determine modifications

How can we detect changes in table data when users add input to cells? For example, if a user clicks on a cell and adds an input, the function should return TRUE to indicate that there are changes. If the user just clicks on the cell without making any ch ...

How can I showcase the captured image on Ionic 2?

I am having trouble displaying the selected or captured image on the page after uploading it through two methods - one using the gallery and the other using the camera. ** Here is my code ** 1) profile.html: <img class="profile-picture" src="{{baseUr ...

Unable to render React component after updating its state

After successfully retrieving data from my API, React is failing to render the cards. export default function Subjects() { const userApi = useUserService(); const auth = useRecoilValue(AuthAtom); const [loading, setLoading] = React.useState<boolea ...

What is the reason behind a tuple union requiring the argument `never` in the `.includes()` method?

type Word = "foo" | "bar" | "baz"; const structure = { foo: ["foo"] as const, bar: ["bar"] as const, baX: ["bar", "baz"] as const, }; const testFunction = (key: keyof typeof sche ...

Is it better to store data individually in localStorage or combine it into one big string?

When it comes to keeping track of multiple tallies in localStorage, one question arises: Is it more efficient to store and retrieve several small data points individually or as one larger chunk? For example: localStorage.setItem('id1', tally1); ...

What is the best approach to validating GraphQL query variables while utilizing Mock Service Worker?

When simulating a graphql query with a mock service worker (MSW), we need to verify that the variables passed to the query contain specific values. This involves more than just type validation using typescript typings. In our setup, we utilize jest along ...

Is there a way to install @types that are compatible with an outdated version of TypeScript?

I am currently working on a project that relies on packages such as @types/express and @types/body-parser. The problem is, the recent updates to these .d.ts files have introduced generic defaults, which now require TypeScript 2.3 or higher. Unfortunately, ...

Tips for binding two elements bidirectionally to a single date module

I am working with two date picker elements, one for selecting months and another for selecting years. I want to establish a two-way binding between these elements and a JavaScript Date object. My inquiry is as follows: Is it feasible to achieve this? If s ...

Ensure that the input field consistently shows numbers with exactly two decimal places

Below is an input field that I want to always display the input value with 2 decimal places. For example, if I input 1, it should show as 1.00 in the input field. How can this be achieved using formControl since ngModel is not being used? Thank you. I att ...

Is there a distinction in Typescript between the return types of Object.seal and .freeze?

I am looking to safeguard the constant object content from any changes during runtime. This includes alterations to both the object structure and its content. The preferred method for achieving this is by using Object.freeze. interface iRO<T> { r ...

Identifying an Incorrect Function Call in a TypeScript Function from a JavaScript File [TypeScript, Vue.js, JavaScript]

I have a vue2 application and I am looking to incorporate TypeScript into some service files without modifying the existing js/vue files. To enable TypeScript support, I utilized vue-cli which allowed me to successfully add a myService.ts file containing ...

The property in the object cannot be assigned because it is read-only: [object Object]

I am currently developing an Ionic application using the Angular framework and NGRX. I have encountered an issue with a selected checkbox. The problem arises when: First, I fetch a list of vehicles from a database using a service. Then, I set the propert ...

What is the best way to connect Classes and Objects in Angular5?

Picture a study tool with flashcards and different categories. Each category has a title, while each card contains content, is linked to only one category, and is also connected to another card. How can I establish these connections in Angular 5 and Types ...

The code within a for loop may not function properly when placed within the ngOnInt() function

I am struggling with running a for loop inside ngOnInit in Angular. I have a service that has a method getAllNews() which returns an array, not an observable. Since I can't use the subscribe() method, I want to iterate over this array. When I console ...

Angular2: Promise Rejection: Quotes cannot be used for evaluation in this component

I'm currently working on a component in Angular that includes an input parameter: import {Component, Input} from '@angular/core'; @Component({ selector: 'comment', template: ` <div class="col-lg-6 col-md-6 ...

What is preventing this from being a function?

It appears that the authenticationProvider is missing for some reason. @autoinject() export class ProviderManager implements AuthenticationManager { constructor( private container: Container ){ } public authenticate( creds: Credentials ): Promis ...

Leverage the template pattern in React and react-hook-form to access a parent form property efficiently

In an effort to increase reusability, I developed a base generic form component that could be utilized in other child form components. The setup involves two main files: BaseForm.tsx import { useForm, FormProvider } from "react-hook-form" expor ...