'TypeScript(err:2532): object is possibly undefined dispite null/undefined check

I've been writing a Discord-bot in TypeScript and I added a command that retrieves information from an airport. The only thing you have to provide is the ICAO code of the airport(4 character code that identifies the airport). Now: Obviously users might get the ICAO code wrong and maybe give invalid ones. So that's why I have a method that retrieves the airport object from a JSON-File and looks a bit like this(not an exact replica, but just so you get an idea of it):

public getAirport(icao: string): Airport | undefined {
    return arrayOfAllAirports.find(ap => ap.icao === icao);
}

In my command file/class I use this method to obviously retrieve the airport with the given ICAO code. Then I check if its return value is undefined and if so, I return *some error* and that is that. And that looks kind of like this:

let airportMgr = new AirportManager();
    
let airport = airportMgr.getAirport(icao);
if (airport === undefined) {
    return *invalid icao error*;
}

*blablabla*

let exampleString = `ICAO: ${airport.icao} | Name: ${airport.name}`;
                                    ^error here             ^error here

But in two files/classes in which I use the said method together with the said checks I get an error further down, that the object might be undefined. Now comes the even more confusing bit: A few lines lower. I access some property of 'airport' again and it doesn't say anything.

Now I know I could use as Airport when I retrieve the airport or reassign it etc, but I want a proper solution and not cheat typescript in stupid ways. Do any of you have an Idea on how to get hold of this issue?

edit:

here's the picture of where it worked: https://i.stack.imgur.com/tTZyJ.png



Solution 1:[1]

The problem is that you are using let, not const. let can be reassigned which means that every time there was a chance it might have been changed, its type will be broadened to its original type. Something like this:

// I made it async to demonstrate something below, for your case it is not important
async function doStuff() {
  // Some icao code
  const icao = 'some icao';
  let airport: Airport | undefined = getAirport(icao);

  // Let's start by checking whether the airport is undefined
  if (airport === undefined) {
    return;
  }

  // And now let's break down all the scenarios that work
  // and then all the ones that don't
  //
  // The important concept here is called lexical scope,
  // an "environment" in which variables and constants are defined

  // 1. current lexical scope - the scope of this function
  //
  // This should work since the value of airport has just been checked
  // and has not had an opportunity to be changed
  airport.name;

  // 2. "child" lexical scope - scope created by a function defined within
  // the current lexical scope
  // 
  // This should not work since you might be calling this function
  // some later time, when the value of airport would have possibly changed
  function someHelperFunction() {
    airport.name;
  }

  // The same applies to promise resolution values
  Promise.resolve().then(() => {
    // This should not work
    airport.name
  });

  // It works with simple awaits
  const anything = await Promise.resolve('something');
  // This will work
  airport.name;

  // A TRICK (!!!)
  //
  // You can at this point create a const of type Airport (because now you are sure
  // it is an airport) and use that in your functions.
  //
  // Since it is defined as const, it cannot be changed 
  // and it will stay an airport like forever and ever
  const airportForSure: Airport = airport;
  function someOtherHelperFunction() {
    // WORKS!!!
    airportForSure.name;
  }

  Promise.resolve().then(() => {
    // Yesss
    airportForSure.name
  });

  // Then when you change the value of airport, you need to check for undefined again
  airport = getAirport(icao);

  // This will not work
  airport.name;
}

type Airport {
  icao: string;
  name: string;
}

// Just a placeholder for a function
declare function getAirport(icao: string): Airport | undefined;

Link to TypeScript playground

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1