Search code examples
typescriptcallbacktypeguards

Why isn't type guard applied in arguments of callback functions?


The following is a simple TypeScript code example.

// Example Setup
declare var foo:{bar?: {baz: string}};
function immediate(callback: () => void) {
  callback();
}

// Type Guard
if (foo.bar) {
  console.log(foo.bar.baz); // ✅ It works!
  immediate(() => {
    console.log(foo.bar.baz); // ❌ TS error : 'foo.bar' is possibly 'undefined'.(18048)
  });
}

foo.bar has a type guard to ensure that it is not a null value, and in fact, foo.bar is inferred to be of type {baz: string}.

However, I don't know why a TypeScript error occurs within the immediate callback function, saying that foo.bar may be undefined. What causes this error?


Solution

  • The reason this happens is that typescript doesn't know when the callback function is going to be called, so it doesn't know if the type narrowing is still valid. Any arbitrary code could have run in between and made changes to foo.

    The simplest solution to this is to assign the thing you're checking to a const. That way, typescript knows it can't change and so the type narrowing must still be valid.

    const temp = foo.bar;
    if (temp) {
      console.log(temp.baz);
      immediate(() => {
        console.log(temp.baz);
      });
    }
    

    PS, version 5.4 of typescript (which is in beta at the time of writing) has improved the handling of some similar cases. But Jcalz tried your case and it will still have this issue. For more information, see this: https://devblogs.microsoft.com/typescript/announcing-typescript-5-4-beta/#preserved-narrowing-in-closures-following-last-assignments