Unusual problem arises with scoping when employing typeguards

Consider the following TypeScript code snippet:

interface A { 
  bar: string;
}
const isA = <T>(obj: T): obj is T & A => {
  obj['bar'] = 'world';
  return true;
}

let obj = { foo: 'hello' };
if (!isA(obj)) throw 'will never be thrown'

obj.foo // This works fine
obj.bar // This works fine

Array(5).fill(0).forEach((_, i) => {
  obj.foo // This works fine
  obj.bar // This does not work
});

Curious why obj.bar is invalid inside the forEach loop?

Typescript Playground

Answer №1

One thing to keep in mind is that sometimes you may outsmart the compiler. TypeScript utilizes certain methods to analyze code control flow in order to deduce narrower types for expressions. While it does a decent job at this, it's not foolproof and may never be.

For example, when you access obj.bar directly after throwing an error if isA(obj) returns false, the compiler narrows down obj to include A as expected. However, things change when you create a closure and pass it to Array.prototype.forEach(). The compiler reverts obj back to its original type that does not include

A</code. Even though we know that <code>forEach()
will immediately call its callback function, TypeScript doesn't have that foresight. It fears that the value of obj could be altered before the callback invocation, leaving it with no option but to give up on narrowing.

A workaround would be to declare obj as a const instead of using let:

const obj = { foo: 'hello' };
if (!isA(obj)) throw 'will never throw'
Array(5).fill(0).forEach((_, i) => {
  obj.bar // This works fine now
});

Although this doesn't guarantee immutability of obj.bar, TypeScript leans towards thinking "const is less likely to change than let", despite evidence suggesting otherwise.

Another way around this issue, if making obj a const isn't possible, is to assign a new const variable post-narrowing, which can then be used in the callback:

let obj = { foo: 'hello' };
if (!isA(obj)) throw 'will never throw'
const myObj = obj;    
Array(5).fill(0).forEach((_, i) => {
  myObj.bar // This is acceptable
});

Alternatively, you could just skip using obj altogether:

let obj = { foo: 'hello' };
if (!isA(obj)) throw 'will never throw'
const bar = obj.bar;    
Array(5).fill(0).forEach((_, i) => {
  bar // This also works
});

The choice is yours. Hopefully, this sheds some light on the situation. Best of luck!

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

Attaching the JSON data to ngModel in Angular 2

I have received a json response containing various fields, including the rewards.rewardName value. I'm trying to figure out how to bind this specific value to [(ngModel)] in Angular 2. [ { "id": 18, "gname": "learning ramayanam", "goalCat ...

Guide on showing error message according to personalized validation regulations in Angular 2?

Utilizing a template-driven strategy for constructing forms in Angular 2, I have successfully implemented custom validators that can be utilized within the template. However, I am facing an issue with displaying specific error messages associated with dis ...

What sets apart exporting a component from importing its module in another module?

When working with Angular, consider having both module A and module B. If I intend to utilize "A-component" within components of module B, what is the distinction between importing module A in Module B compared to including the "A-component" in the exports ...

``Can someone provide guidance on how to showcase the validation check result for a text-field in the snackbar using Vuet

One of the challenges I'm facing in my project is implementing a validation function for the customer form. While using the vuetify validate method would be the easy way to go, I need to display the validation messages both as snackbar and next to eac ...

Unlocking the potential: passing designated text values with Javascript

In my current React code, I am retrieving the value from cookies like this: initialTrafficSource: Cookies.get("initialTrafficSource") || null, Mapping for API const body = {Source: formValue.initialTrafficSource} Desired Output: utmcsr=(direct)|utmcmd=(n ...

I'm interested in learning how to implement dynamic routes in Nexy.js using TypeScript. How can I

I have a folder structure set up like this: https://i.stack.imgur.com/qhnaP.png [postId].ts import { useRouter } from 'next/router' const Post = () => { const router = useRouter() const { pid } = router.query return <p>Post: {p ...

Verify whether the default export of a file is a React function component or a standard function

Trying to figure out how to distinguish between modules exporting React function components and regular functions. Bun employs file-based routing, enabling me to match requests with module files to dynamically import based on the request pathname. Conside ...

Adding ngrx action class to reducer registration

Looking to transition my ngrx actions from createAction to a class-based approach, but encountering an error in the declaration of the action within the associated reducer: export enum ActionTypes { LOAD_PRODUCTS_FROM_API = '[Products] Load Products ...

Accessing information from an Odata controller in Angular2

Greetings as a first-time question asker and long-time reader/lurker. I've been delving into learning angular2, but I'm facing some challenges when it comes to retrieving data from an odata controller. In my simple Angular 2 application, I'm ...

What is the reason that the protected keyword is not retained for abstract properties in TypeScript?

I'm uncertain whether this issue in TypeScript is a bug or intended functionality. In my Angular project, I have 3 classes - an abstract service, a service that implements the abstract service, and a component that utilizes the implementing service. ...

Guide on releasing a TypeScript component for use as a global, commonJS, or TypeScript module

I have developed a basic component using TypeScript that relies on d3 as a dependency. My goal is to make this component available on npm and adaptable for use as a global script, a commonJS module, or a TypeScript module. The structure of the component is ...

Why does my useEffect consistently execute after the initial rendering, despite having specified dependencies?

const [flag, setFlag] = React.useState(false) const [username, setUsername] = React.useState('') const [password, setPassword] = React.useState('') const [errorUsername, setErrorUsername] = React.useState(true) const [er ...

Navigating through the nested object values of an Axios request's response can be achieved in React JS by using the proper

I am attempting to extract the category_name from my project_category object within the Axios response of my project. This is a singular record, so I do not need to map through an array, but rather access the entire object stored in my state. Here is an ex ...

Encountering an error while trying to update an Angular application to version 10

I am currently running a demo app and I am new to Angular. Below is my category list component. import { Component, OnInit } from '@angular/core'; import { Router } from '@angular/router'; import { Observable } from 'rxjs'; im ...

The parent component remains visible in the Angular router

Within my appComponent, I have a layout consisting of a toolbar, footer, and main content. The main content utilizes the router-outlet directive structured as follows: <div class="h-100"> <app-toolbar></app-toolbar> < ...

Capture a screenshot with Puppeteer at a random URL stop

I am facing an issue with my service nodejs running on Ubuntu, where I use puppeteer to capture screenshots of pages. However, the method page.screenshot({fullPage: true, type: 'jpeg'}) sometimes fails on random URLs without displaying any errors ...

What steps should I take to enable users of my library to customize component templates as needed?

I created a customized component to enhance the appearance of bootstrap form controls, intended for use in various projects. I am considering transforming this into a library (a separate npm package with its own @NgModule), but some projects may wish to mo ...

Can you explain the significance of using curly braces in an import statement?

The TypeScript handbook has a section on Shorthand Ambient Modules, where an import statement is shown as: import x, {y} from "hot-new-module"; It doesn't explain why y is in curly braces in the above statement. If both x and y were inside the brace ...

"Embrace the powerful combination of WinJS, Angular, and TypeScript for

Currently, I am attempting to integrate winjs with Angular and TypeScript. The Angular-Winjs wrapper functions well, except when additional JavaScript is required for the Dom-Elements. In my scenario, I am trying to implement the split-view item. Although ...

Unleashing the Power of Typescript and SolidJS: Expanding the Properties of JSX Elements

Is there a way to enhance the props of an existing JSX element in SolidJS and craft a custom interface similar to the ButtonProps interface shown in this React example below? import Solid from 'solid-js'; interface ButtonProps extends Solid.Butt ...