简体   繁体   中英

Typescript: variable possibly undefined inside anonymous function

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.

// Typescript Playground link


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 .

TypeScript does not check types and even change types at runtime, so baseDirId type would always is string | undefined string | undefined unless you do narrow types or something else for type, so there are many options you can try.

1. Use default

let baseDirId = args.baseDirId || "valid string"

2. Do a conditional value check

if(args.baseDirId){
  let baseDirId = args.baseDirId
  // ...
  // do something you want
}

But you can't do this directly in following snippet, since you used let to declare baseDirId , and then it won't work due to that it can be changed to undefined at any time unless it's declared via const

if(baseDirId){
  const bleharr = someArr.map((val) => rstr(baseDirId))
}

3. Use ! non-null assertion operator

When you're sure it must be exsiting and you don't want to change anything else

const bleharr = someArr.map((val) => rstr(baseDirId!))

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM