Mastering Typescript: A guide to effectively narrowing types on promises

I'm currently exploring how to effectively implement typeguards on promises based on specific parameters.

function request({ logic }: { logic: boolean }) {
    return new Promise((resolve, reject) => {
        if (l)
            resolve("something");
        resolve(1);
    });
}

request({ logic: true }).then(a => {
    a.length
})

In this instance, my goal is to ensure that the typeof 'a' is equal to 'string'. I've attempted to create some typeguards within request but their outcomes seem to disappear. It's unclear whether this is due to a TypeScript limitation or if I need to employ clever type casting techniques.

This simple example reflects my current objective of crafting an asynchronous call with slightly different result variations based on certain parameters. I would prefer not to create another function simply to address a modified return type.

Answer №1

Using Typescript function overloading for better clarity:

function process(request: true): Promise<string>;
function process(request: false): Promise<number>;
function process(request: boolean) {
    return new Promise((resolve, reject) => {
        if (request) 
            resolve("something");
        resolve(1);
    });
}

process(true).then(result => {
    console.log(result.length); //<-- correctly identifies result as a string
});

process(false).then(result => {
    console.log(result.length); //<-- error: property 'length' does not exist on type number
});

Typeguards are important to use within if statements.

UPDATE

An interesting fact! Typescript also supports overloading distinction based on fields. See the code snippet below:

function process(options: { request: true }): Promise<string>;
function process(options: { request: false }): Promise<number>;
function process(options: { request: boolean }) {
    return new Promise((resolve, reject) => {
        if (options.request) 
            resolve("something");
        resolve(1);
    });
}


process({ request: true }).then(result => {
    console.log(result.length); //<-- recognizes that result is a string
});

process({ request: false }).then(result => {
    console.log(result.length); //<-- error: property length cannot be found on type number
});

UPDATE

By using generics, you can achieve the desired behavior. In this way, only the request field matters from the caller's perspective. However, there may be a loss of typecheck even for options.request within the implementation of the process functions.

function process<T extends { request: true }>(options: T): Promise<string>;
function process<T extends { request: false }>(options: T): Promise<number>;
function process(options: any) {
    return new Promise((resolve, reject) => {
        if (options.request) 
            resolve("something");
        resolve(1);
        console.log(options.anything);
    });
}

process({ request: true, foo: 'bar' }).then(result => {
    console.log(result.length); //<-- correctly identifies result as a string
});

process({ request: false, foo: 'baz' }).then(result => {
    console.log(result.length); //<-- error: property length cannot be found on type number
});

Answer №2

The importance of proper overload implementation cannot be understated (consider adding types):

function fetchData(data: boolean): Promise<string>;
function fetchData(data: boolean): Promise<number>;
function fetchData(data: boolean): Promise<any>;
function fetchData(data: boolean) {
    return new Promise((resolve, reject) => {
        if (data) 
            resolve("something");
        resolve(1);
    });
}

fetchData(true).then((result) => {
    console.log(result.length); //<-- identifies result as a string
});

fetchData(false).then((result) => {
    console.log(result.length); //<-- error: 'length' property does not exist on number
});

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

Concealed Selenium File Upload through Drag and Drop User Interface

Struggling to insert an image into the specified input using selenium web-driver. <input type="file" multiple="multiple" class="dz-hidden-input" accept="image/gif,image/jpg,image/jpeg,image/png,application/zip" style="visibility: hidden; position: abso ...

Sorry, it seems like there is an issue with the Typescript error that states: "The expression you are trying to call is not valid. The type 'typeof import("koa-session")

Partially resolved: An issue has been identified on Github regarding this problem. It seems that declaring a module in a global scope rewrites the types of the entire exported module, while declaring a module within another module merges the types. This b ...

The function `this.someFunction(params)` is invalid and cannot be used in Angular 4

Incorporating d3.js into an Angular 4 application has been my current project. However, I encountered a problem trying to call a function which resulted in this error: ERROR TypeError: this.highlightNodes is not a function. This is the snippet of code tha ...

The jQuery click event to change the color function is functioning properly, but there seems to be an issue with

I'm having trouble changing the font-family using the code below, even though the color is changing properly. What am I missing here and how can I correct it? $(document).ready(function(){ $("body").on("click",".selectableColor", function(){ ...

Perform an asynchronous request using a data variable retrieved from a previous asynchronous request

I have a function using ajax to parse XML data. Here is an example: $.ajax({ type: "GET", url: "the.xml", dataType: "xml", success: function parseXml(data){ $(data).find("ITEM").each(function(){ var x = $("URL", this).t ...

Ajax: The seamless integration of loading multiple images simultaneously

I have a grid of images (3x3) that need to be updated periodically. Each image is loaded independently through an ajax callback method, like this: for (var i=0; i < numImages; i++) { Dajaxice.loadImage(callback_loadImage, {'image_id':i}) } ...

Tips on viewing class object values within the `useEffect` hook

App.js import React, { useRef, useEffect } from "react"; import Token from "./Token"; export default function App() { const tokenRef = useRef(new Token()); useEffect(() => { console.log("current index of token: ", ...

Service in Angular2+ that broadcasts notifications to multiple components and aggregates results for evaluation

My objective is to develop a service that, when invoked, triggers an event and waits for subscribers to return data. Once all subscribers have responded to the event, the component that initiated the service call can proceed with their feedback. I explore ...

Retrieving and updating the attribute value for d3.symbolTriangle

Currently, I am utilizing d3.symbolTriangle to create equilateral triangles in my project. I am interested in learning how to access and modify the attribute values. This is a snippet of my code: var body = d3.select("body"); var triangle = d3.symbol() ...

What are the best practices for utilizing intro.js effectively on mobile devices?

Description When using introjs on mobile devices with a screen width of less than 600px, the tooltip ends up covering the element. When I hold the device horizontally, the tooltip adjusts its position accordingly. I attempted to apply custom CSS to the too ...

Material UI Grid has a problem with inconsistent column width when using direction='column' and flexWrap='wrap' attribute combination

I'm a newcomer to React and Frontend development in general. Currently, I am working on setting up a MUI Grid that organizes items in columns which wrap around when necessary. So far, I've been able to accomplish this by adjusting the direction a ...

Property missing in Typescript type definition

In my Typescript Next project, I am using this component: import PageTitle from './pagetitle' import style from './contact.styl' export default function Contact() { return ( <section> <a name="contact"> ...

Utilizing Jquery during a partial postback within an updatepanel

I am facing an unusual issue related to JQuery and partial postback on an updatepanel. What I have tried is implementing a jQuery logic in the code-behind using: Page.ClientScript.RegisterStartupScript(this.GetType(), "jsSlider" + select.ClientID, sb.ToS ...

Invalid action has been dispatched by the effect:

In my project, I am using Angular 7.1.4. This is an excerpt from my effect code: @Injectable() export class LoginEffects { constructor(private actions$: Actions, p ...

The issue arises when the parameter value is used in conjunction with jQuery's :not()

Currently, I am developing a simple functionality for radio buttons that activates an input text box when clicked. If any other radio button is selected, the input field should go back to its disabled state. Initially, everything was working smoothly unti ...

Can React code be converted to HTML, CSS, and JavaScript?

I am currently working on creating a website using React without any backend, solely relying on components and other similar tools. My goal is to convert it into HTML, CSS, and JavaScript files for easier publishing. I have heard that Angular offers a feat ...

Error: The data type '(number | undefined)[]' cannot be converted to type 'number[]'

Transitioning to Typescript in my NextJS application has presented a challenge that I cannot seem to overcome. The error arises on the line value={value} within the <Slider.Root> The variable value comprises of two numeric elements: a min and a max. ...

Recurring occurrences of Ajax ViewComponent being triggered

I have encountered a problem with an on-click Ajax event that is triggering a Controller action/ViewComponent multiple times. The issue arises when I utilize Ajax on a button click to call a Controller Action, which inserts data into the database and then ...

What is the reason behind TypeScript's lack of support for exporting with decorators?

The issue regarding this problem was addressed on https://github.com/tc39/proposal-decorators/issues/69. Despite this discussion, TypeScript does not currently support it. The following code demonstrates the lack of support: export @deco() class a {}; H ...

I am utilizing React to pull data from the database with a one-day discrepancy in the backend

Despite trying numerous solutions, I am still puzzled as to why this issue persists. For instance, in my database, there is a date listed under the "date_of_birth" column as 2023-12-29. However, when examining my backend code, it displays as "date_of_birth ...