Why does this undefined check not work with a find function? Why does this undefined check not work with a find function? typescript typescript

Why does this undefined check not work with a find function?


I really liked this question and done some research, and learnt something. As OP mentioned it has to do with closure, specifically the callback. Please read my comments in the code below where I try to explain things as they come.

const f1 = (value: number) => !!value;const f2 = (x: { y: number | undefined}): void => {    if (x.y === undefined || !isFinite(x.y)) {        throw new Error('Not a number');    }    // Here, you have ensured here that type of z is not changing    const z = x.y;    // `x.y` is mutable,     // As jcalz correctly mentioned in the comment,     // the compiler does not know that the `find` function executes the     // callback synchronously and immediately, and it is expensive    // to check if anyone has altered its type elsewhere before the     // callback was executed. So, it can't    // guarantee that the type of `x.y` stays number    // hence, Typescript expands `x.y` back to `number | undefined`    ['stuff', 'here'].find(item => f1(x.y));        // This however knows type isn't changing    ['stuff', 'here'].find(item => f1(z));    // However, this works because Typescript optimistically thinks    // the function, in our case `find`, does not have a side effect    // (referring the article noted below)    f1(x.y);    // In a parallel universe, where `find` is     // asynchronous like `timeout`,    // and executes the callback sometime later    // and we had the following line here    x.y = undefined    // Since the `find` function, in our parallel universe,     // would call the callback    // after we have assigned `x.y` as `undefined`    // it makes sense to expand the type in the callback within the find    // function back to number | undefined};

Here is an interesting article on this. Really good read: https://herringtondarkholme.github.io/2017/02/04/flow-sensitive/


The problem is that inside of the arrow function the compiler is not certain that the type narrowing is still valid.

const f1 = (value: number) => !!value;const f2 = (x: { y: number | undefined}): void => {    if (x.y === undefined || !isFinite(x.y)) {        throw new Error('Not a number');  }  // z is `number`, so use `z` again in the arrow function to "keep" the type narrowing  const z = x.y;  ['stuff', 'here'].find(item => f1(z));};

TypeScript Playground