TypeScript 規(guī)范項(xiàng)目錯(cuò)誤處理

在 JavaScript 開(kāi)發(fā)中,通常都不太重視起錯(cuò)誤處理,捕獲和記錄錯(cuò)誤對(duì)于任何項(xiàng)目的開(kāi)發(fā)周期都是至關(guān)重要的。隨著 TypeScript 項(xiàng)目開(kāi)發(fā)多了,開(kāi)始意識(shí)到并不真正了解錯(cuò)誤處理。經(jīng)常在項(xiàng)目代碼中看到一下類(lèi)似代碼:
try {
throw new Error("Oops")
} catch (error) {
console.error(error.message)
}
error 是 unknown 類(lèi)型 ,因此在將其轉(zhuǎn)換為新類(lèi)型或縮小類(lèi)型范圍之前,不能對(duì) error 執(zhí)行任何操作。正確的處理方式是縮小類(lèi)型,將看看如何做到這一點(diǎn),但為什么這是必要的?
在 JavaScript 中,幾乎任何東西都可以被拋出:
throw "oops"
throw 210
throw null
throw { message: "異常錯(cuò)誤" }
所以真正被捕獲的錯(cuò)誤是未知的。但是,可以通過(guò)使用 TypeScript 的多種方式干凈地處理錯(cuò)誤。
JavaScript錯(cuò)誤的基礎(chǔ)知識(shí)
JavaScript 中的錯(cuò)誤類(lèi)型,在 JavaScript 中有許多類(lèi)型的錯(cuò)誤,但最常見(jiàn)的是:
ReferenceError:代碼引用了一個(gè)不存在的變量。TypeError:值不是預(yù)期的錯(cuò)誤類(lèi)型SyntaxError:代碼在語(yǔ)法上無(wú)效
拋出錯(cuò)誤
有時(shí)需要手動(dòng)拋出錯(cuò)誤,例如,可能有一些代碼依賴(lài)于函數(shù)調(diào)用的返回值,但有可能該值是 undefined,或者至少在 TypeScript 認(rèn)為是 undefined。在下面這個(gè)例子中,拋出是縮小返回用戶范圍的最佳解決方案。
// 通常方式
function createProject() {
const user = getUser();
saveProject({ name: "", userId: user.id })
}
// 避免異常
function createProject() {
const user = getUser();
if (!user) {
return;
}
saveProject({ name: "", userId: user.id })
}
// 最佳方式,拋出異常
function createProject() {
const user = getUser();
if (!user) {
throw new ReferenceError('用戶不存在')
}
saveProject({ name: "", userId: user.id })
}
捕獲錯(cuò)誤
一旦錯(cuò)誤被拋出,它將在調(diào)用堆棧中冒泡,直到被 try/catch 語(yǔ)句捕獲。當(dāng)在 try 塊內(nèi)運(yùn)行的代碼拋出錯(cuò)誤時(shí),它將在 catch 塊中被捕獲,錯(cuò)誤可能源自嵌套在函數(shù)內(nèi)部的函數(shù),并且會(huì)冒泡直到被捕獲。
try {
throw new ReferenceError();
} catch (error) {
console.error(error)
}
縮小錯(cuò)誤類(lèi)型
一旦被捕獲,檢查所拋出的錯(cuò)誤類(lèi)型可能很有用。這使能夠?qū)㈩?lèi)型從未知縮小到可以與之交互的特定類(lèi)型(可以直觀的理解錯(cuò)誤),可以用 instanceof 做到這一點(diǎn):
try {
throw new ReferenceError();
} catch (error) {
if (error instanceof ReferenceError) {
console.error(error.message)
}
}
設(shè)計(jì)模式
設(shè)計(jì)模式是軟件設(shè)計(jì)中常見(jiàn)問(wèn)題的解決方案,這些模式很容易重復(fù)使用并且富有表現(xiàn)力。在最新的項(xiàng)目中,將代碼按域分組在名為 Features 的目錄中,它可以包含相關(guān)的組件、鉤子、類(lèi)型、錯(cuò)誤等等,每個(gè) Feature 目錄都包含一個(gè) errors.ts 文件,在其中為各自的域定義了一個(gè)自定義錯(cuò)誤類(lèi)。
- 《ES6 類(lèi)聊 JavaScript 設(shè)計(jì)模式之創(chuàng)建型模式》
- 《ES6 類(lèi)聊 JavaScript 設(shè)計(jì)模式之結(jié)構(gòu)型模式》
創(chuàng)建自定義錯(cuò)誤類(lèi)型
在 errors.ts 文件中,導(dǎo)出了一個(gè) class。為潛在名稱(chēng)維護(hù)一個(gè)聯(lián)合類(lèi)型,這增加了一些不錯(cuò)的智能感知和類(lèi)型安全。該類(lèi)擴(kuò)展了 Error 對(duì)象,它允許插入堆棧跟蹤(對(duì)于大多數(shù) JS 運(yùn)行時(shí))。
type ErrorName =
'GET_PROJECT_ERROR' | 'CREATE_PROJECT_ERROR' | 'PROJECT_LIMIT_REACHED';
export class ProjectError extends Error {
name: ErrorName;
message: string;
cause: any;
constructor({ name, message, cause }: {
name: ErrorName;
message: string;
cause?: any;
}) {
super();
this.name = name;
this.message = message;
this.cause = cause;
}
}
拋出自定義錯(cuò)誤
實(shí)例化新錯(cuò)誤時(shí),name 值具有智能感知,并且必須是聯(lián)合類(lèi)型中定義的名稱(chēng)之一。
export async function createProject() {
const { data, error } = await api.createProject();
if (error) {
throw new ProjectError({
name: "CREATE_PROJECT_ERROR",
message: "API error occurred while creating project",
cause: error
})
}
if (data.length === projectLimit) {
throw new ProjectError({
name: "PROJECT_LIMIT_REACHED",
message: "Project limit has been reached."
})
}
return data;
}
捕獲自定義錯(cuò)誤
當(dāng)錯(cuò)誤被捕獲時(shí),可以使用 instanceof 縮小錯(cuò)誤類(lèi)型。一旦縮小范圍,error.name 就會(huì)智能感知,此時(shí)可以根據(jù)拋出的錯(cuò)誤名稱(chēng)執(zhí)行邏輯。在此示例中,PROJECT_LIMIT_REACHED 錯(cuò)誤是要向用戶顯示的錯(cuò)誤,提供了一條專(zhuān)門(mén)為用戶呈現(xiàn)的消息。
try {
await createProject();
} catch (error) {
if (error instanceof ProjectError) {
if (error.name === "PROJECT_LIMIT_REACHED") {
toast(error.message)
}
}
}
定義可重用的錯(cuò)誤庫(kù)
由于項(xiàng)目中有很多 errors.ts 文件,類(lèi)中唯一的動(dòng)態(tài)代碼是名稱(chēng)的聯(lián)合類(lèi)型,因此可以對(duì)代碼進(jìn)行優(yōu)化,創(chuàng)建了一個(gè) ErrorBase 類(lèi),它接受用作名稱(chēng)類(lèi)型的泛型。
export class ErrorBase<T extends string> extends Error {
name: T;
message: string;
cause: any;
constructor({ name, message, cause }: { name: T, message: string, cause?: any }) {
super();
this.name = name;
this.message = message;
this.cause = cause
}
}
現(xiàn)在,當(dāng)創(chuàng)建一個(gè)新的自定義錯(cuò)誤類(lèi)時(shí),可以擴(kuò)展這個(gè)基類(lèi),需要做的就是給它提供可用名稱(chēng)的聯(lián)合類(lèi)型。
import { ErrorBase } from "./error-base"
type ErrorName =
'GET_PROJECT_ERROR' | 'CREATE_PROJECT_ERROR' | 'PROJECT_LIMIT_REACHED';
export class TeamError extends ErrorBase<ErrorName>{ }
總結(jié)
設(shè)計(jì)模式讓代碼變得更容易維護(hù),處理錯(cuò)誤只是維護(hù)良好的應(yīng)用程序的一部分,另一個(gè)重要步驟是使用類(lèi)似 Sentry 的工具記錄跟蹤錯(cuò)誤。