TypeScript 解决缩小未知数的方法

2eafrhcq  于 6个月前  发布在  TypeScript
关注(0)|答案(6)|浏览(42)

建议

🔍 搜索词

可选链未知

✅ 可实施性检查清单

无,因为这不是关于任何特定实现的问题

问题

这似乎是目前全面/安全处理错误的方式:

try {
  someApiCall()
} catch (error) {
  if (error && typeof error === 'object') {
    if ('response' in error && error.response && typeof error.response === 'object') {
      if ('message' in error.response && typeof error.response.message === 'string') {
        return { error: error.response.message }
      }
    }
  }
  return { error: 'Unknown error' }
}

⭐ 建议

我发现上述方法很难使用。如果能有更容易的方法就太好了,这样就不会诱使人将其转换为 any 并冒着运行时错误。

daolsyd0

daolsyd01#

2 potential options:

Optional chaining for unknown

Allowing optional chaining for unknown was suggested before, which would make the code look like this:

try {
  someApiCall()
} catch (error) {
  if (error?.response?.response && typeof error.response.response === 'string') {
    return { error: error.response.message }
  }
  return { error: 'Unknown error' }
}

If I understand correctly, it was turned down because it would make it too easy to create logic errors by accessing properties that are never supposed to exist. I personally think it would rather help prevent errors because of how much simpler it would be to comprehensively handle errors instead of just casting to any .

Optional chaining for union

Another option could be to allow optional chaining for properties that are declared in a union:

type Error = { error: { response: string } }
try {
  someApiCall()
} catch (error: unknown | Error) {
  if (error?.response?.response && typeof error.response.response === 'string') {
    return { error: error.response.message }
  }
  return { error: 'Unknown error' }
}

This would mean unknown | T is different from unknown . This might have implications for other unions too though, unless it's a special case for unknown , so not sure about this

goqiplq2

goqiplq22#

看起来你正在描述 Partial<Error>

4dbbbstv

4dbbbstv3#

如果错误(或某些属性)是null,那么使用Partial<Error>是否会导致运行时错误?

xqnpmsa8

xqnpmsa84#

使用 Partial<Error> 存在不便之处和潜在的运行时错误:
您可以解决这些问题,但我真希望 TypeScript 能有一个明显、简单、方便且内置的解决方案,可以在访问捕获到的错误的属性时避免运行时错误。

  1. 如果抛出的值是 nullish(空值),Partial<Error> 将导致运行时错误。虽然 JavaScript 允许这样做,但 TypeScript 也可以。至少您可以通过检查错误对象中的空值来相当干净地解决这个问题:
// error:  Partial<{ response: { message: string } }>
if (error && typeof error.response?.message === 'string')

// comparison
if (typeof error?.response?.message === 'string')

2。您仍然可能在尝试访问嵌套属性时遇到运行时错误。
虽然在示例代码中不是一个问题,但对代码进行一些简化可能会导致这种情况发生。

type CustomError = { response: { message: string } }
try {
  someApiCall()
} catch (error: Partial<CustomError>) { // ignoring the separate issue that this is not allowed
  if (typeof error?.response.message === 'string') { // runtime error for e.g. { response: { status: 1111 }, no TS error
    return { error: error.response.message }
  }
  return { error: 'Unknown error' }
}

您可以使用未内置的 DeepPartial 的实现或手动将所有内容类型化为可选(冒着错过某个地方的风险)。
3. 您不能将错误注解为 Partial,只能为 anyunknown。您可以通过在每个使用它的地方Assert类型来解决这个问题,但这样 if 就无法作为类型守卫(缩小)工作了。

type CustomError = { response: { message: string } }
try {
  someApiCall()
} catch (error) {
  if (typeof (error as  Partial<CustomError>)?.response.message === 'string') { 
    return { error: error.response.message } // TS error: error is unknown. You would have to assert here as well.
  }
  return { error: 'Unknown error' }

您可以通过创建自己的类型守卫来解决这个问题。

type CustomError = { response: { message: string } }
try {
  someApiCall()
} catch (error) {
  if (isCustomError(error) ) { 
    return { error: error.response.message }
  }
  return { error: 'Unknown error' }
const isCustomError = (error: unknown): error is CustomError => 
  typeof (error as Partial<CustomError)?.response.message === 'string'

或者您可以将其保存为单独的变量(可能是最方便的方法)。

type CustomError = { response: { message: string } }
try {
  someApiCall()
} catch (error) {
  const customError = error as Partial<CustomError>
  if (typeof error?.response.message === 'string') { 
    return { error: error.response.message }
  }
  return { error: 'Unknown error' }

4。在一个类型中重复每个属性访问可能非常繁琐,尤其是如果它不会被重用。
以下是一个 Package 器代码,用于 Package 所有 API 交互。在将 Package 结果传递给调用者之前,它会记录关键信息。它容易出错(缺少 ?),并且需要很多努力和困惑(这不是值的完整类型,我们也不完全信任它)。即使有 DeepPartial(我没有想到),对于一个只使用一次的类型,也需要付出很多努力和额外的代码。

try {
  ...
} catch (error) {
  const result = error as Untrusted
  log.debug('rate limit issue, {
'x-appminlimit-remaining': result?.response?.headers?.['x-appminlimit-remaining'],
'correlation-id': result?.response?.headers?.['correlation-id'],
'x-minlimit-remaining': result?.response?.headers?.['x-minlimit-remaining'],
'x-daylimit-remaining': result?.response?.headers?.['x-daylimit-remaining'],
'x-rate-limit-problem': result?.response?.headers?.['x-rate-limit-problem'],
'retry-after': result?.response?.headers?.['retry-after'],
tenantId: result?.response?.request?.headers?.['tenant-id'],
});
throw error
}

type Untrusted =
| {
response?: IncomingMessage & {
headers?: {
'x-appminlimit-remaining'?: unknown;
'x-minlimit-remaining'?: unknown;
'x-daylimit-remaining'?: unknown;
'x-rate-limit-problem'?: unknown;
'retry-after'?: | unknown;
'tenant-id'?: | unknown;
'correlation-id'?: | unknown;
};
request?: { headers?: { 'tenant-id'?: | unknown } };
        };
    }
  | undefined
  | null;

在花费了相当多的时间尝试让 TypeScript 在不付出太多努力的情况下警告我之后,我倾向于只是使用 any 并用可选链式语法涂抹我的代码,希望我不会在无意中遗漏它或将 any 分配给其他东西。
我还尝试了以下自定义类型(在 #37700(评论)中建议),它效果很好,但需要Assert错误的类型,并经常Assert正在访问的属性值(我通常可以Assert为 unknown)。

yqyhoc1h

yqyhoc1h5#

我知道这在这种情况下并不完全适用,但我有时希望:

try {
  someFn();
} catch(e: Error) {
  // ...
} catch(e: SomeOther) {
  // ...
} catch(e: unknown) {
  // ...
}

是语法糖

try {
  someFn();
} catch(e: unknown) {
  if (e instanceof Error) {
    // ...
  } else if (e instanceof SomeOther) {
    // ...
  } else {
    // ...
  }
}

,并且如果你跳过 catch(e: unkown) 部分,它会对你大喊大叫。
我知道 instanceof 并不总是适用的,就像上面的例子一样,但一个人可以梦想。

gcuhipw9

gcuhipw96#

复合类型也存在同样的问题

type HasId = {id: number};
type Foo = {foo: number};
type Bar = {bar?: {value: number}};

type Composite = (HasId & Foo) | (HasId & Bar) | undefined;

declare const myValue: Composite;

if (myValue?.bar) { // <- expected: should be feasable but typescript doesn't accept this - error "bar" does not exist on "myValue"
    console.log(myValue.bar.value)
}

if (myValue && 'bar' in myValue && myValue.bar) { // <- actual
    // 1. you must use `'x' in y` syntax
    // 2. you must check for undefined first, as `in` is not allowed on undefined
    // 3. you have to `myValue.bar` anyway, as `'x' in y` doesn't check for `null`
    console.log(myValue.bar.value)
}

有时候使用 'x' in y 确实很繁琐。

相关问题