最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

關于在Typescript中做錯誤處理的方式詳解

 更新時間:2023年09月07日 09:09:16   作者:托兒所夜十三  
錯誤處理是軟件工程重要的一部分,如果處理得當,它可以為你節(jié)省數小時的調試和故障排除時間,我發(fā)現了與錯誤處理相關的三大疑難雜癥:TypeScript的錯誤類型,變量范圍和嵌套,讓我們逐一深入了解它們帶來的撓頭問題,感興趣的朋友可以參考下

錯誤處理是軟件工程重要的一部分。如果處理得當,它可以為你節(jié)省數小時的調試和故障排除時間。我發(fā)現了與錯誤處理相關的三大疑難雜癥:

  • TypeScript 的錯誤類型
  • 變量范圍
  • 嵌套

讓我們逐一深入了解它們帶來的撓頭問題。

疑難雜癥一:Typescript 錯誤類型

在 JavaScript 中最常見的錯誤處理方式與大多數編程語言相同:

try {
  throw new Error('oh no!')
} catch (error) {
  console.dir(error)
}

最終會拋出這樣一個對象:

{
  message: 'oh no!'
  stack: 'Error: oh no!\n at <anonymous>:2:8'
}

這看起來非常簡單明了,那么 Typescript 又是怎樣的呢? 首先你能看到的是在 Typescript 中使用 try/catch 并檢查錯誤類型是,得到的是 unknow。 對于剛接觸 Typescript 的人來說遇到這種問題是非常撓頭的。解決這一問題的常用方法是簡單地將錯誤轉為其他類型,如下所示:

try {
  throw new Error('oh no!')
} catch (error) {
  console.log((error as Error).message)
}

這種方法可能適用于 99.9% 的捕獲錯誤。但為什么 TypeScript 的錯誤處理看起來很麻煩呢?原因在于無法推斷出 "error" 的類型,因為 try/catch 并不只捕獲錯誤,它還捕獲任何拋出的錯誤。在 JavaScript(和 TypeScript)中,幾乎可以拋出任何東西,如下所示:

try {
  throw undefined
} catch (error) {
  console.log((error as Error).message)
}

執(zhí)行這段代碼將導致在 "catch "代碼塊中拋出新的錯誤,這就沒有達到使用 try/catch 的目的:

Uncaught TypeError: Cannot read properties of undefined (reading 'message') at <anonymous>:4:20

問題產生的原因是 undefined 中不存在 message 屬性,從而導致在 catch 代碼塊中出現 TypeError。在 JavaScript 中,只有兩個值會導致這個問題:undefined 和 null。

現在可能有人會問,有人拋出 undefined 或 null 的可能性有多大。雖然這種情況可能很少發(fā)生,但如果真的發(fā)生了,就會在代碼中引入意想不到的行為。此外,考慮到在 TypeScript 項目中通常會使用大量第三方包,如果其中一個包無意中拋出了一個不正確的值,也不足為奇。

這就是 TypeScript 將可拋類型設置為 unknow 的唯一原因嗎?乍一看,這可能只是一個罕見的邊緣情況,使用類型轉換是一個比較靠譜的解決方式。然而,事情并非如此簡單。雖然 undefined 和 null 是最具破壞性的情況,因為它們可能導致應用程序崩潰,但其他值也可能被拋出。例如:

try {
  throw false
} catch (error) {
  console.log((error as Error).message)
}

這里的主要區(qū)別在于,它不會拋出 TypeError,而是直接返回 undefined。雖然這不會直接導致應用程序崩潰,因此破壞性較小,但也會帶來其他問題,例如在日志中顯示未定義。此外,根據使用undefined 值的方式,它還可能間接導致應用程序崩潰。請看下面的示例:

try {
  throw false
} catch (error) {
  console.log((error as Error).message.trim())
}

在這里,調用 undefined 上的 .trim() 將觸發(fā) TypeError,可能導致應用程序崩潰。

從本質上講,TypeScript 的目的是通過將 catchables 的類型指定為 unknow 來保護我們。這種方法讓開發(fā)人員有責任確定拋出值的正確類型,有助于防止出現運行時問題。

如下所示,您可以使用可選的鏈式操作符 (?.) 來保護您的代碼:

try {
  throw undefined
} catch (error) {
  console.log((error as Error)?.message?.trim?.())
}

雖然這種方法可以保護你的代碼,但它使用了兩個會使代碼維護復雜化的 TypeScript 特性:

  • 類型轉換破壞了 TypeScript 的保障措施,即確保變量遵循其指定的類型。
  • 在非可選類型上使用可選的鏈式操作符,在類型不匹配的情況下,如果有人遺漏了這些操作符,也不會引發(fā)任何錯誤。

更好的方法是利用 TypeScript 的類型保護。類型保護本質上是一種函數,它能確保特定值與給定類型相匹配,并確認可以安全地按預期使用。下面是一個類型保護的示例,用于驗證捕獲的變量是否屬于 Error 類型:

export const isError = (value: unknown): value is Error =>
  !!value &&
  typeof value === 'object' &&
  'message' in value &&
  typeof value.message === 'string' &&
  'stack' in value &&
  typeof value.stack === 'string'

這種類型防護簡單明了。它首先確保值不是假的,這意味著它不會是 undefined 或 null。然后,它會檢查它是否是一個具有預期屬性的對象。

這種類型保護可以在代碼的任何地方重復使用,以驗證對象是否是 Error。下面是一個應用示例:

const logError = (message: string, error: unknown): void => {
  if (isError(error)) {
    console.log(message, error.stack)
  } else {
    try {
      console.log(
        new Error(
          `Unexpected value thrown: ${
            typeof error === 'object' ? JSON.stringify(error) : String(error)
          }`
        ).stack
      )
    } catch {
      console.log(
        message,
        new Error(`Unexpected value thrown: non-stringifiable object`).stack
      )
    }
  }
}
try {
  const circularObject = { self: {} }
  circularObject.self = circularObject
  throw circularObject
} catch (error) {
  logError('Error while throwing a circular object:', error)
}

通過創(chuàng)建一個利用 isError 類型防護的 logError 函數,我們可以安全地記錄標準錯誤以及任何其他拋出的值。這對于排除意外問題特別有用。不過,我們需要謹慎,因為 JSON.stringify 也會拋出錯誤。通過將其封裝在自己的 try/catch 塊中,可以為對象提供更詳細的信息,而不僅僅是記錄其字符串表示 [object Object]

此外,我們還可以檢索新 Error 對象實例化之前的堆棧跟蹤。這將包括拋出原始值的位置。雖然該方法不能直接提供拋出值的堆棧跟蹤,但它提供了拋出后的跟蹤,足以追溯到問題的源頭。

疑難雜癥二:變量范圍

范圍界定可能是錯誤處理中最常見的疑難雜癥,適用于 JavaScript 和 TypeScript。請看下面這個例子:

try {
  const fileContent = fs.readFileSync(filePath, 'utf8')
} catch {
  console.error(`Unable to load file`)
  return
}
console.log(fileContent)

在本例中,由于 fileContent 是在 try 代碼塊內定義的,因此在該代碼塊外無法訪問。為了解決這個問題,你可能會想在 try 代碼塊之外定義變量:

let fileContent
try {
  fileContent = fs.readFileSync(filePath, 'utf8')
} catch {
  console.error(`Unable to load file`)
  return
}
console.log(fileContent)

這種方法并不理想。使用 let 而不是 const,就意味著變量是可變的,這會帶來潛在的錯誤。此外,它還會增加代碼的閱讀難度。

規(guī)避這一問題的方法之一是將 try/catch 代碼塊封裝在一個函數中:

const fileContent = (() => {
  try {
    return fs.readFileSync(filePath, 'utf8')
  } catch {
    console.error(`Unable to load file`)
    return
  }
})()
if (!fileContent) {
  return
}
console.log(fileContent)

雖然這種方法解決了可變性問題,但卻使代碼變得更加復雜。我們可以通過創(chuàng)建自己的可重用封裝函數來解決這個問題。

疑難雜癥三:嵌套

下面的示例演示了如何在可能出現多個錯誤的情況下使用新的 logError 函數:

export const doStuff = async (): Promise<void> => {
  try {
    const fetchDataResponse = await fetch('https://api.example.com/fetchData')
    const fetchDataText = await fetchDataResponse.text()
    if (!fetchDataResponse.ok) {
      throw new Error(
        `Unexpected response while fetching data. Status: ${fetchDataResponse.status} | Status text: ${fetchDataResponse.statusText} | Body: ${fetchDataText}`
      )
    }
    let fetchData
    try {
      fetchData = JSON.parse(fetchDataText) as unknown
    } catch {
      throw new Error(`Failed to parse fetched data response as JSON: ${fetchDataText}`)
    }
    if (
      !fetchData ||
      typeof fetchData !== 'object' ||
      !('data' in fetchData) ||
      !fetchData.data
    ) {
      throw new Error(
        `Fetched data is not in the expected format. Body: ${fetchDataText}`
      )
    }
    const storeDataResponse = await fetch('https://api.example.com/storeData', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
      },
      body: JSON.stringify(fetchData),
    })
    const storeDataText = await storeDataResponse.text()
    if (!storeDataResponse.ok) {
      throw new Error(
        `Unexpected response while storing data. Status: ${storeDataResponse.status} | Status text: ${storeDataResponse.statusText} | Body: ${storeDataText}`
      )
    }
  } catch (error) {
    logError('An error occurred:', error)
  }
}

你會發(fā)現調用的是 .text() API,而不是 .json()。因為 fetch 能調用這兩種方法中的一種。由于我們的目標是在 JSON 轉換失敗時顯示正文內容,因此首先調用 .text(),然后手動還原為 JSON,確保在此過程中捕捉到任何錯誤。為避免出現以下隱含錯誤:

Uncaught SyntaxError: Expected property name or '}' in JSON at position 42

雖然錯誤提供的細節(jié)會使代碼更容易調試,但其有限的可讀性會給代碼維護帶來挑戰(zhàn)。try/catch 塊引起的嵌套增加了閱讀函數時的認知負擔。不過,有一種方法可以簡化代碼,如下所示:

export const doStuffV2 = async (): Promise<void> => {
  try {
    const fetchDataResponse = await fetch('https://api.example.com/fetchData')
    const fetchData = (await fetchDataResponse.json()) as unknown
    if (
      !fetchData ||
      typeof fetchData !== 'object' ||
      !('data' in fetchData) ||
      !fetchData.data
    ) {
      throw new Error('Fetched data is not in the expected format.')
    }
    const storeDataResponse = await fetch('https://api.example.com/storeData', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
      },
      body: JSON.stringify(fetchData),
    })
    if (!storeDataResponse.ok) {
      throw new Error(`Error storing data: ${storeDataResponse.statusText}`)
    }
  } catch (error) {
    logError('An error occurred:', error)
  }
}

這次重構解決了嵌套問題,但也帶來了一個新問題:錯誤報告的粒度不夠。通過刪除檢查,變得更加依賴錯誤信息本身來理解問題。正如我們從一些 JSON.parse 錯誤中看到的那樣,這并不總能提供最好的顆粒度。

考慮到我們討論的所有的疑難雜癥,是否存在有效處理錯誤的最佳方法?

解決方案

應該尋求一種比傳統(tǒng)的 try/catch 塊更優(yōu)越的錯誤處理方法。通過利用 TypeScript 的功能,我們可以毫不費力地為此制作一個封裝函數。

第一步是確定希望如何規(guī)范化錯誤。下面是一種方法:

export class NormalizedError extends Error {
  stack: string = ''
  /** The original value that was thrown. */
  originalValue: unknown
  /**
   * Initializes a new instance of the `NormalizedError` class.
   *
   * @param error - An `Error` object.
   * @param originalValue - The original value that was thrown.
   */
  constructor(error: Error, originalValue?: unknown) {
    super(error.message)
    this.stack = error.stack ?? this.message
    this.originalValue = originalValue ?? error
    Object.setPrototypeOf(this, NormalizedError.prototype)
  }
}

擴展 Error 對象的主要優(yōu)點是它的行為與標準錯誤類似。從頭開始創(chuàng)建一個自定義錯誤對象可能會導致復雜問題,尤其是在使用 instanceof 操作符檢查其類型時。這就是為什么要顯式地設置原型,以確保 instanceof 能正確工作,尤其是當代碼被移植到 ES5 時。

此外,Error 的所有原型函數在 NormalizedError 對象上都可用。構造函數的設計還簡化了創(chuàng)建新 NormalizedError 對象的過程,因為它要求第一個參數必須是一個實際的 Error。以下是 NormalizedError 的優(yōu)點:

  • 由于構造函數要求第一個參數必須是 Error,因此它始終是一個有效的錯誤。
  • 添加了一個新屬性 originalValue。這可以檢索拋出的原始值,這對于從錯誤中提取附加信息或在調試過程中非常有用。
  • 堆棧永遠不會是未定義的。在許多情況下,記錄堆棧屬性比記錄消息屬性更有用,因為它包含更多信息。然而,TypeScript 將其類型定義為 string | undefined,這主要是出于跨環(huán)境兼容性的考慮(在傳統(tǒng)環(huán)境中經常出現)。通過重寫類型并保證其始終為字符串,可以簡化其使用。

既然已經定義了標準化錯誤的表示方法,就需要一個函數將 unknow 的拋出值轉換為標準化錯誤:

export const toNormalizedError = <E>(
  value: E extends NormalizedError ? never : E
): NormalizedError => {
  if (isError(value)) {
    return new NormalizedError(value)
  } else {
    try {
      return new NormalizedError(
        new Error(
          `Unexpected value thrown: ${
            typeof value === 'object' ? JSON.stringify(value) : String(value)
          }`
        ),
        value
      )
    } catch {
      return new NormalizedError(
        new Error(`Unexpected value thrown: non-stringifiable object`),
        value
      )
    }
  }
}

使用這種方法,不再需要處理 unknow 類型的錯誤。所有錯誤都將是合適的 Error 對象,從而為我們提供盡可能多的信息,并消除出現意外錯誤值的風險。

為了安全地使用 NormalizedError 對象,我們還需要一個類型保護函數:

export const isNormalizedError = (value: unknown): value is NormalizedError =>
  isError(value) && 'originalValue' in value && value.stack !== undefined

現在,我們需要設計一個函數,幫助我們避免使用 try/catch 。另一個需要考慮的關鍵問題是錯誤的發(fā)生,它可以是同步的,也可以是異步的。理想情況下,我們需要一個能同時處理這兩種情況的函數。首先,讓我們創(chuàng)建一個類型保護來識別 Promise

export const isPromise = (result: unknown): result is Promise<unknown> =>
  !!result &&
  typeof result === 'object' &&
  'then' in result &&
  typeof result.then === 'function' &&
  'catch' in result &&
  typeof result.catch === 'function'

有了安全識別 Promise 的能力,就可以繼續(xù)實現新的 noThrow 函數了:

type NoThrowResult<A> = A extends Promise<infer U>
  ? Promise<U | NormalizedError>
  : A | NormalizedError
export const noThrow = <A>(action: () => A): NoThrowResult<A> => {
  try {
    const result = action()
    if (isPromise(result)) {
      return result.catch(toNormalizedError) as NoThrowResult<A>
    }
    return result as NoThrowResult<A>
  } catch (error) {
    return toNormalizedError(error) as NoThrowResult<A>
  }
}

通過利用 TypeScript 的功能,我們可以動態(tài)支持異步和同步函數調用,同時保持準確的類型。這樣,我們就可以使用單個實用程序函數來管理所有錯誤。

此外,如前所述,這對解決范圍問題特別有用??梢院唵蔚厥褂?noThrow,而不用將 try/catch 封裝在自己的匿名自調用函數中,這樣代碼的可讀性就大大提高了。

下面是一個重構版本:

export const doStuffV3 = async (): Promise<void> => {
  const fetchDataResponse = await fetch('https://api.example.com/fetchData').catch(toNormalizedError)
  if (isNormalizedError(fetchDataResponse)) {
    return console.log('Error fetching data:', fetchDataResponse.stack)
  }
  const fetchDataText = await fetchDataResponse.text()
  if (!fetchDataResponse.ok) {
    return console.log(
      `Unexpected response while fetching data. Status: ${fetchDataResponse.status} | Status text: ${fetchDataResponse.statusText} | Body: ${fetchDataText}`
    )
  }
  const fetchData = noThrow(() => JSON.parse(fetchDataText) as unknown)
  if (isNormalizedError(fetchData)) {
    return console.log(
      `Failed to parse fetched data response as JSON: ${fetchDataText}`,
      fetchData.stack
    )
  }
  if (
    !fetchData ||
    typeof fetchData !== 'object' ||
    !('data' in fetchData) ||
    !fetchData.data
  ) {
    return console.log(
      `Fetched data is not in the expected format. Body: ${fetchDataText}`,
      toNormalizedError(new Error('Invalid data format')).stack
    )
  }
  const storeDataResponse = await fetch('https://api.example.com/storeData', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
      },
      body: JSON.stringify(fetchData),
    }).catch(toNormalizedError)
  if (isNormalizedError(storeDataResponse)) {
    return console.log('Error storing data:', storeDataResponse.stack)
  }
  const storeDataText = await storeDataResponse.text()
  if (!storeDataResponse.ok) {
    return console.log(
      `Unexpected response while storing data. Status: ${storeDataResponse.status} | Status text: ${storeDataResponse.statusText} | Body: ${storeDataText}`
    )
  }
}

這樣就解決了所有的疑難雜癥:

  • 類型現在可以安全使用,因此不再需要 logError,可以直接使用 console.log 來記錄錯誤。
  • 使用 noThrow 可以控制范圍,在定義 const fetchData 時就證明了這一點,以前必須使用 let fetchData。
  • 嵌套已減少到單層,使代碼更易于維護。

你可能還注意到,我們在 fetch 時沒有使用 noThrow。相反,使用了 toNormalizedError,其效果與 noThrow 差不多,但嵌套更少。由于我們構建 noThrow 函數的方式,你可以在獲取時使用它,就像我們在同步函數中使用它一樣:

const fetchDataResponse = await noThrow(() =>
    fetch('https://api.example.com/fetchData')
  )

總結

在不斷變化的軟件開發(fā)環(huán)境中,錯誤處理仍然是穩(wěn)健應用程序設計的基石。正如我們在本文中所探討的,try/catch 等傳統(tǒng)方法雖然有效,但有時會導致代碼結構復雜,尤其是在結合 JavaScript 和 TypeScript 的動態(tài)特性時。通過使用 TypeScript 的功能,展示了一種精簡的錯誤處理方法,它不僅簡化了我們的代碼,還增強了代碼的可讀性和可維護性。

NormalizedError 類和 noThrow 實用功能的引入展示了現代編程范式的強大功能。這些工具允許開發(fā)人員從容地處理同步和異步錯誤,確保應用程序在面對突發(fā)問題時仍能保持彈性。

以上就是關于在Typescript中做錯誤處理的方案詳解的詳細內容,更多關于Typescript錯誤處理的資料請關注腳本之家其它相關文章!

相關文章

  • JavaScript實現短暫提示框功能

    JavaScript實現短暫提示框功能

    這篇文章主要介紹了JavaScript實現短暫提示框功能,非常不錯,具有參考借鑒價值,需要的朋友可以參考下
    2018-04-04
  • 一文詳解JavaScript中的URL和URLSearchParams

    一文詳解JavaScript中的URL和URLSearchParams

    URL,稱為統(tǒng)一資源定位器,指互聯網上能找到資源定位的字符串,而URLSearchParams對象是專門用于處理url網址信息中的查詢字符串,本文就來帶大家深入了解一下二者的使用
    2023-05-05
  • 深入理解JavaScript函數參數(推薦)

    深入理解JavaScript函數參數(推薦)

    這篇文章主要介紹了深入理解JavaScript函數參數(推薦)的相關資料,需要的朋友可以參考下
    2016-07-07
  • async/await地獄該如何避免詳解

    async/await地獄該如何避免詳解

    這篇文章主要給大家介紹了關于async/await地獄該如何避免的相關資料,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧。
    2018-05-05
  • JavaScript變速動畫函數封裝添加任意多個屬性

    JavaScript變速動畫函數封裝添加任意多個屬性

    這篇文章主要介紹了JavaScript變速動畫函數封裝添加任意多個屬性 ,非常不錯,具有一定的參考借鑒價值,需要的朋友可以參考下
    2019-04-04
  • ES6?class類實現繼承實例詳解

    ES6?class類實現繼承實例詳解

    傳統(tǒng)的javascript中只有對象,沒有類的概念,下面這篇文章主要給大家介紹了關于ES6?class類實現繼承的相關資料,文中通過實例代碼介紹的非常詳細,需要的朋友可以參考下
    2022-10-10
  • 原生js實現照片墻效果

    原生js實現照片墻效果

    這篇文章主要介紹了原生js實現照片墻效果,幫助大家更好的利用js制作特效,感興趣的朋友可以了解下
    2020-10-10
  • JavaScript對象數組排序實例方法淺析

    JavaScript對象數組排序實例方法淺析

    這篇文章主要介紹了JavaScript對象數組排序實例方法淺析的相關資料,非常不錯,具有參考借鑒價值,感興趣的朋友一起學習吧
    2016-06-06
  • 微信小程序 下拉列表的實現實例代碼

    微信小程序 下拉列表的實現實例代碼

    這篇文章主要介紹了微信小程序 下拉列表的實現實例代碼的相關資料,需要的朋友可以參考下
    2017-03-03
  • js學習總結之DOM2兼容處理重復問題的解決方法

    js學習總結之DOM2兼容處理重復問題的解決方法

    這篇文章主要為大家詳細介紹了js學習總結之DOM2兼容處理重復問題的解決方法,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2017-07-07

最新評論

上犹县| 石阡县| 常宁市| 琼结县| 额尔古纳市| 渑池县| 潼关县| 沁源县| 留坝县| 泸州市| 华安县| 晋江市| 基隆市| 桐梓县| 垫江县| 黄浦区| 房产| 自治县| 乐山市| 阜宁县| 玉树县| 汝州市| 东光县| 雷山县| 灵宝市| 蓝田县| 那曲县| 霍林郭勒市| 察隅县| 寿宁县| 夹江县| 阿瓦提县| 株洲县| 乌鲁木齐市| 陆川县| 米林县| 石嘴山市| 永丰县| 昭平县| 绥阳县| 三台县|