TLDR; Checking variable before using it in a anonymous function still TS warns variable possibly undefined
In the below code example variable baseDirId
is checked if undefined then passed to array.map function but TS warns baseDirId
can be undefined.
const rstr = async (a: string) => {
return a + "bleh"
}
const args: { baseDirId: string | undefined } = {
baseDirId: "someid"
// baseDirId: undefined
}
const someArr = ["bleh1", "bleh2"]
const func1 = async (): Promise<void> => {
try {
// Assume baseDirId can be undefined
let baseDirId = args.baseDirId
// Trigger if baseDirId is undefined
if (!baseDirId) {
const baseDirObj = { id: "valid string" }
baseDirId = baseDirObj.id
}
console.log(typeof baseDirId)
// baseDirId cant be anything other than a string
if (typeof baseDirId !== "string") {
return Promise.reject("Base Dir response invalid")
}
// Why is baseDirId `string | undefined` inside rstr call below even after above checks
const bleharr = someArr.map((val) => rstr(baseDirId))
console.log(await Promise.all(bleharr))
} catch (err) {
console.error(err)
}
}
func1().catch(err => console.error(err))
Is there any possible case where baseDirId
can be undefined
?
Why wont TS allow it ? Better way to do it ?
Let's slightly change the code to
return () => someArr.map((val) => rstr(baseDirId))
so instead of calling .map
directly it might get run at a later point in time. Some other code might've written undefined into baseDirId
in the meantime. Therefore to correctly infer the type, Typescript would've to check that no other code somewhen overrides the variable. That's quite a complicated task (it could even be impossible in some corner cases). Also this gets even more complicated if our inner function was called at multiple places:
let mutable: string | undefined = /*...*/;
const inner = () => fn(mutable); // << how to infer this?
mightCall(inner); // if called back here, mutable would be "string | undefined"
if(typeof mutable === "string") mightCall(inner); // if called here, mutable could be narrowed down to "string", but only if mightCall calls back synchronously
mutable = undefined; // if mightCall calls back asynchronously, 'mutable' would have to be inferred as undefined
Therefore when functions access variables from the outer scope, the Typescript compiler assumes the widest possible type. Type narrowing only works for the function's body itself. To narrow down the type you'd either need a type assertion, or alternatively copy the value into a const
:
let mutable: string | undefined = /*...*/;
if(typeof mutable !== "string") return;
// mutable get's narrowed down to string here due to the typeof type guard
const immutable = mutable;
// ^ inferred as string, this is the widest possible type
This also works in your case.