Home > Mobile >  Understanding undefined key in object when using map
Understanding undefined key in object when using map

Time:11-21

This is the MWE:

interface Animals {cat:string, dog:string}

let a: Animals|undefined;


if(a){// if we got `a` from somewhere
 [1,2,3].map(v=>a.cat)
 // ----------> ~ Object is possibly 'undefined'
}

Playground

I am not sure what exactly the error is, it seems to be related to map outputting something or undefined, but why a isn't defined?

CodePudding user response:

This is a general limitation of TypeScript, described in microsoft/TypeScript#9998. The effects of control flow analaysis, such as the narrowing of a from Animals | undefined to Animals following the truthiness check if (a), do not persist to closed-over values across function boundaries.

Inside the body of the callback v => a.cat, therefore, there has been no control flow narrowing of a; it is considered to be of type Animals | undefined, and therefore you get an error when indexing into it.

The reason this happens is because there is currently no way for the compiler to know that the callback v => a.cat is run immediately. That's out-of-band information we have about Array.prototype.map(), but from the type system's perspective there's no meaningful difference between [1,2,3].map and the following definition of foo:

function foo(cb: (x: number) => string) {
    setTimeout(() => cb(100), 1000);
}

a = { cat: "abc", dog: "def" };
if (a) {
    foo(v => a.cat) // error!
    // ----> ~ Object is possibly 'undefined'
}
a = undefined;
// later: Uncaught TypeError: a is undefined

The foo() function takes a callback, and calls it later. And in fact, when it calls it, a is undefined. So the compiler is, in general, right to complain. You could hope that the compiler might notice whether or not a is ever reassigned and suppress the error if it isn't, but that's a lot of extra work for the compiler to do, and the point of microsoft/TypeScript#9998 is that they haven't found anything yet that would pay for itself in terms of compiler performance.

There's a feature request at microsoft/TypeScript#11498 to allow the type system to be told that map() runs its callback immediately, so that any control flow analysis results can be persisted inside the callback, which would allow map() and foo() to be treated differently... but for now it's not part of the language.


So that's the problem. The compiler doesn't know that the callback will be run immediately, and it does not scour the source code to check if a is ever reassigned to undefined, so it errs on the safe side.

Currently the best workaround would be to assign your narrowed value to a new const variable, whose type is by definition already narrowed:

if (a) {
    const _a = a;
    [1, 2, 3].map(v => _a.cat) // okay
}

The _a variable, where it exists, is always of type Animals, not Animals | undefined, so the callback type checks successfully.

Playground link to code

CodePudding user response:

This happens because of how TypeScript handles uncertainty of variable reassignment. Here is the discussion on design choice. If you swap your declaration for a const the error will be cleared in the flow section and mark it at the initialization.

  • Related