Conditional statements in Typescript can be utilized when dealing with unknown data types

In one of my projects, I have a function that executes an asynchronous operation over a collection. I want to add typings to this function for better IDE support - specifically, I want the IDE to infer that the first parameter in the callback function is of the same type as the elements in the array.

Here is the original function:

export async function asyncForEach(array, callback) {
    for (let index = 0; index < array.length; index++) {
        await callback(array[index], index, array);
    }
}

I tried adding explicit typings like this, but encountered a problem:

export async function asyncForEach<T>(
    items: T[],
    callback: (item: T, idx: number, items: T[]) => Promise<any>
): Promise<void> {
    for (let index = 0; index < items.length; index += 1)
        await callback(items[index], index, items);
}

However, with this setup, I faced the following scenarios:

  • When the first parameter was a typed array <T>[], the first callback parameter was correctly inferred as <T>
  • When the first parameter was an untyped array [], the first callback parameter was correctly inferred as any
  • PROBLEM: When the first parameter was an "any" typed variable, the first callback parameter was now inferred as "unknown" instead of "any". This change caused build issues with legacy code that relied on accessing properties of the "any" variable, which broke when the variable became "unknown".

I attempted to use a ternary operator in the callback parameter typing to address this issue, but it did not work successfully - the compiler failed to build and type inference stopped working.

export async function asyncForEach<T>(
    items: T[],
    callback: (item: T extends unknown ? any : T, idx: number, items: T[]) => Promise<any>
): Promise<void> {
    for (let index = 0; index < items.length; index += 1)
        await callback(items[index], index, items);
}

If anyone knows how to resolve the

(item: T extends unknown ? any: T, 
part, I would greatly appreciate your help.

Thank you in advance for any assistance provided.

Answer №1

It is questionable whether the approach you are taking is advisable; personally, I would suggest that if someone decides to pass an items parameter of the risky any type that disables type checking wherever it is used, they should be prepared for the consequences. In this scenario, the consequence is that the compiler cannot deduce a specific type for T, hence it defaults to unknown (as of TypeScript 3.5).

If you prefer the compiler to assign a different default, then you can specify one by using the = operator in the type parameter declaration:

export async function asyncForEach<T = any>(
    items: T[],
    callback: (item: T, idx: number, items: T[]) => Promise<any>
): Promise<void> {
    for (let index = 0; index < items.length; index += 1)
        await callback(items[index], index, items);
}

Note the T = any above. This results in the following behavior:

declare const a: any;
asyncForEach(a, async (i) => i.randomThingBecauseAnyDoesntTypeCheck); // okay

This way, you achieve the desired outcome without compromising on the expected behavior when dealing with well-typed items:

asyncForEach([a], async (i) => i.randomThingBecauseAnyDoesntTypeCheck); // okay
asyncForEach([1, 2, 3], async (i) => i.toFixed()); // okay

Link to code playground for reference

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

Is it possible to utilize an InterleavedBufferAttribute for index values?

I am troubleshooting a code snippet that is throwing an error: const geometry = new THREE.BufferGeometry(); const indices = new THREE.InterleavedBufferAttribute(...); geometry.setIndex(indices); // this is invalid After running this code, I receive a com ...

Utilizing Jest to Simulate a Class - Incorporation via Imported Module

I'm having difficulty mocking a constructor of a class from an imported module. Interestingly, it works perfectly when my mock implementation is directly inserted into the jest.mock() factory function, but fails when the implementation is imported fro ...

Using StencilJS to Incorporate CSS/SASS Styles from node_modules

I'm currently facing a challenge in importing a CSS file containing variables from a node_modules package. Despite trying to replicate the process outlined in stencil.config.ts, the builds continue to end up in a different location than intended, leav ...

What is the best way to encrypt the JWT decode token payload using Angular 8 on the client-side?

Currently, I am utilizing angular2-jwt to handle the code following an http.get request. In order to properly send an http post request, I must encode the body. let body = { "sub": "1234567890", "name": "John Doe", "iat": 1516239022 }; this.http.pos ...

Exploring the StackNavigationProps and Screen properties in react-navigation v5 for Typescript

When dealing with defining types for a screen's navigation prop in a different file than the router, what is the most effective approach? For example, if I have one file where routes are defined: //Router.tsx type RootStackParamList = { Home: unde ...

I'm encountering an issue when attempting to send a parameter to a function within a typescript code

Recently, I started using Typescript and encountered an issue with passing arguments to a function in Typescript. This particular function is triggered when rendering a form modal. However, I keep receiving two errors: "Argument of type 'Promise& ...

Encountering a module resolve error when a tsx file is added to the project item group

After setting up an asp.net core project with a react template and configuring Typescript, I created a simple file named Test.tsx with the following code: import React from 'react'; class Test extends React.Component { render() { r ...

Assuming control value accessor - redirecting attention

import { Component, Input, forwardRef, OnChanges } from '@angular/core'; import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms'; @Component({ selector: 'formatted-currency-input', templateUrl: '../v ...

Determining the type of a utilized generic function

When working with TypeScript, it is possible to determine the type of a function by using the following method: function exampleFunc(param: number) {} type ExampleFuncType = typeof exampleFunc; // RESULT: (param: number) => void If the function is gen ...

Display notification if the request exceeds the expected duration

Is there a way to display a message when a request is taking too long? For instance, if the request exceeds 10 seconds in duration, I want to show a message asking the user to wait until it finishes. fetchData(url, requestParams) { return this.restServic ...

Create a hierarchical tree structure using a string separated by dots

I am struggling with organizing a tree structure. :( My goal is to create a tree structure based on the interface below. export type Tree = Array<TreeNode>; export interface TreeNode { label: string; type: 'folder' | 'file'; ...

PlatypusTS: Embracing Inner Modules

Incorporating angular, I have the capability to fetch object instances or import modules using the $injector in this manner: export class BaseService { protected $http: angular.IHttpService; protected _injector: angular.auto.IInjec ...

Guide to successfully submitting an Angular form that includes a nested component

I have developed a custom dateTime component for my application. I am currently facing an issue where I need to integrate this component within a formGroup in a separate component. Despite several attempts, I am unable to display the data from the child fo ...

Guide to developing universal customized commands in Vue 3 using Typescript

I recently built my app using the Vue cli and I'm having trouble registering a global custom directive. Can anyone point out what I might be doing incorrectly here? import { createApp } from "vue"; import App from "./App.vue"; impo ...

Need at least one of two methods, or both, in an abstract class

Consider the following scenario: export abstract class AbstractButton { // Must always provide this method abstract someRequiredMethod(): void; // The successor must implement one of these (or both) abstract setInnerText?(): void; abst ...

Enriching SpriteWithDynamicBody with Phaser3 and Typescript

Is there a way to create a custom class hero that extends from SpriteWithDynamicBody? I'm unable to do so because SpriteWithDynamicBody is only defined as a type, and we can't extend from a type in Typescript. I can only extend from Sprite, but ...

Type ' ' cannot be assigned to type ''..ts(2322) ANOTHA ONE

Being a beginner in TypeScript and currently learning about enums, I encountered an error with the following example code that I cannot seem to understand. Here's the code snippet: enum Status { SUCCESS = 'success', FAILED = 'fa ...

After reloading the component, I encountered difficulties subscribing again to the BehaviorSubject for a second time

After reading recommendations to always unsubscribe when removing a component, I encountered an issue in my code. The error object unsubscribed occurs when navigating between child components and trying to resubscribe to a BehaviorSubject. Even though I am ...

Guide to utilizing services in Angular 2

As I've developed a service with numerous variables and functions, my goal is to inject this service into multiple components. Each component should have the ability to update certain variables within the service so that all variables are updated once ...

To collapse a div in an HTML Angular environment, the button must be clicked twice

A series of divs in my code are currently grouped together with expand and collapse functionality. It works well, except for the fact that I have to click a button twice in order to open another div. Initially, the first click only collapses the first div. ...