Typescript fails to recognize a value assigned within an await statement

Looking at the code snippet below, we see that the variable x starts off undefined and can later be assigned a value of 1 within an `await` promise.

Despite setting x to 1 inside the awaited promise, TypeScript still perceives it as undefined after the promise completes.

Is there an error in my approach here?

async function test() {
  let x:number | undefined = undefined
  await new Promise(res => {
    x = 1
    res(x)
  })
  if (x) {
    console.log(x)
  }
}

Playground

Answer №1

This limitation in TypeScript's capability to conduct control flow analysis is widely recognized. Typically, the compiler can narrow down the presumed type of a union-typed variable upon assignment. However, it fails to track the outcomes of control flow analysis across function boundaries. The compiler operates under the assumption that called functions do not impact the apparent type of any variables. For an authoritative explanation of this issue, refer to microsoft/TypeScript#9998.

When initializing

let x:number | undefined = undefined
, the compiler narrows down the type of x from number | undefined to just undefined. By nature, the compiler cannot predict if the callback function res => { x = 1; res(x) } will ever be executed. Therefore, it chooses not to assume that x holds the value 1. To maintain type safety, the compiler would need to consider the possibility of the function call and revert the type of x back to number | undefined. Yet, doing so would render control flow analysis virtually worthless. Hence, TypeScript adopts an "optimistic" heuristic approach by assuming that function calls are devoid of side effects, even though this assumption may be incorrect in certain cases.


In your scenario, apart from potential runtime modifications, my recommendation would either involve utilizing a type assertion in the declaration of x, allowing you to prevent the initial narrowing to undefined:

async function test() {
  let x = undefined as number | undefined;
  await new Promise(res => {
    x = 1
    res(x)
  })
  // x is number | undefined here
  if (x) {
    x.toFixed(2); // no error now
  }
}

Alternatively, you could employ a type assertion later to inform the compiler that the value is truly 1 despite its lack of realization:

async function test2() {
  let x: number | undefined = undefined;
  await new Promise(res => {
    x = 1
    res(x)
  });
  (x as number | undefined as 1).toFixed(2); // okay
}

Unfortunately, there isn't a straightforward method to widen x itself without affecting runtime (x = x as number | undefined as 1 could work but has runtime implications).

Access the code on Playground

Answer №2

The code that assigns the variable x to 1 is contained within a separate function. Although you and I understand that the promise constructor will invoke this function synchronously, TypeScript lacks the capability to make such assumptions. In JavaScript, there are numerous instances where callback functions are executed asynchronously, such as in the case of setTimeout. Due to the absence of timing information in type annotations, TypeScript assumes asynchronous execution, resulting in no impact on the assigned type of x post-await.

If you are ultimately resolving the promise with a value of 1, it would be advisable to modify your code as follows:

let x: number | undefined = undefined
x = await new Promise<number>(res => {
  res(1)
})

By implementing this change, TypeScript recognizes that a promise returning a number is being utilized. Furthermore, after awaiting the promise and assigning its resolved value to x, TypeScript accurately infers that x will hold a numerical value.

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

Sending the HTML input value to a Knockout view

Can someone assist me with a dilemma I'm facing? Within CRM on Demand, I have a view that needs to extract values from CRM input fields to conduct a search against CRM via web service. If duplicate records are found, the view should display them. Be ...

TypeScript interface designed to capture objects containing a flexible number of values

In my possession is an object that looks like the following: { "0001": "a", "0002": "b", "0003": "c", ... } Is it possible for me to create a TypeScript interface that accurately represents this type? ...

The Angular 2 Router's navigation functionality seems to be malfunctioning within a service

Currently, I am facing an issue with using Angular2 Router.navigate as it is not functioning as expected. import { Injectable } from '@angular/core'; import { Http, Headers } from '@angular/http'; import { Router } from '@angular/ ...

Extract objects from a nested array using a specific identifier

In order to obtain data from a nested array of objects using a specific ID, I am facing challenges. My goal is to retrieve this data so that I can utilize it in Angular Gridster 2. Although I have attempted using array.filter, I have struggled to achieve t ...

Using conditional statements to render content based on a certain condition within a

One of my requirements is to dynamically render a React component in the following manner: parent.ts ... <Parent> <Child/> <Parent> ... child.ts ... return (someBoolean && <Component/>) ... While ...

What is the meaning of '=>' in typescript/javascript?

I keep coming across lots of '=>' in the code I found on the internet. Could someone please explain it to me as if I were 5 years old? (I'm searching for the specific code, and I'll share it here once I locate it).. Found it: ...

Navigating through nested data in React TypeScript can be accomplished by accessing the nested data

How can data in a nested interface like the one shown below be accessed in React TypeScript? export interface School { prices: Array<{ state: { response_header?: { school_type_opportunities?: Array<{ benefit_type_opportunity?: st ...

Create a tuple type by mapping an object with generics

I have a specified object: config: { someKey: someString } My goal is to generate a tuple type based on that configuration. Here is an example: function createRouter< T extends Record<string, string> >(config: T) { type Router = { // ...

Launch a fresh window in Angular application without the need for a complete restart

How can I open a new window in Angular while passing values in the route to call an endpoint without causing the entire application to reload? It feels like such a hassle just to display a simple HTML page. Is there a better way to achieve this? ...

Can one extract a property from an object and assign it to a property on the constructor?

Currently working with TypeScript, I am looking to destructure properties from an object. The challenge lies in the fact that I need to assign it to a property on the constructor of the class: var someData = [{title: 'some title', desc: 'so ...

​Troubleshooting findOneAndUpdate in Next.js without using instances of the class - still no success

After successfully connecting to my MongoDB database and logging the confirmation, I attempted to use the updateUser function that incorporates findOneAndUpdate from Mongoose. Unfortunately, I ran into the following errors: Error: _models_user_model__WEBPA ...

Update the Angular component once new data is saved in a separate Angular component

I've been diving into the world of MEAN stack and Angular, tackling a method within an angular component called itemListDetailsComponent (found in the .ts file) that looks something like this: onStatusUpdateClick() { this.activatedRoute.queryPar ...

Use an Angular array filter to return only values that match

In my dataset, each client is associated with a set of cases. I am looking to extract only those clients who have cases with the status "Open". Clients with cases marked as "Closed" should not be included in the filtered list. Currently, my filtering metho ...

Checking if a route path is present in an array using Typescript and React

Here is a sample of my array data (I have simplified it here, but there are approximately 100 elements with about 20 values each): 0: odata.type: "SP.Data.ProductListItem" Title: "This is Product 1" Id: 1 1: odata.type: "SP.Data.ProductListItem" Title: ...

Is there any need for transpiling .ts files to .js when Node is capable of running .ts files directly?

If you are using node version 12, try running the following command: node hello.ts I'm curious about the purpose of installing typescript globally with npm: npm install -g typescript After that, compiling your TypeScript file to JavaScript with: ...

Struggling to figure out webhooks with Stripe

I have encountered a strange issue while using Stripe webhooks to process payments on my website. When I set the currency to USD, it prompts me to provide an address outside of India, which is expected. However, when I change the currency to INR, the addre ...

The instantiation of generic types in Typescript

I have been working on a function that aims to create an instance of a specified type with nested properties, if applicable. This is the approach I have come up with so far. export function InitializeDefaultModelObject<T extends object> (): T { ...

Navigating to child components within an Angular module is currently disabled

My Project Dashboard contains 2 sub apps. ├───projects │ ├───app1 │ │ ├───e2e │ │ │ └───src │ │ └───src │ │ ├───app │ │ │ ├───form │ ...

What is the best way to duplicate a Typescript class object while making changes to specific properties?

I have a Typescript cat class: class Kitty { constructor( public name: string, public age: number, public color: string ) {} } const mittens = new Kitty('Mittens', 5, 'gray') Now I want to create a clone of the inst ...

Jest | Testing Tool for Functions with Multiple Parameters

I have developed a new tool that requires 3 parameters: value (string), service (any - an actual GET service), and name (string). Below is the code for the tool: import { serverErrorResponseUtil } from 'util/serverErrorResponseUtil'; import { H ...