電腦在執行程式的時候,經常會遇到預期的或是非預期的錯誤,TypeScript當然也不例外。這個章節要來介紹TypeScript的「try-catch」錯誤處理機制。
throw
關鍵字
TypeScript的throw
關鍵字,可以讓JavaScript程式在任意位置中斷執行,並拋出錯誤訊息。這個錯誤訊息可以是任意值(包括undefined
)。
例如:
console.log("Hello");
throw "I am an error!";
console.log("World");
以上程式,會先輸出Hello
文字到主控台,接著當程式執行到第三行時,字串I am an error!
就會被當作是錯誤訊息被「拋出」。由於第三行敘述的位置並不是在「try-catch」的try
區塊下,因此被拋出的錯誤訊息會直接被輸出到主控台中,且若JavaScript環境是Node.js的話,JavaScript程式會立刻結束執行。
try-catch
利用try
和catch
關鍵字組成兩個程式敘述區塊,可以將try
區塊中拋出的錯誤訊息給「攔截」下來。當有錯誤訊息被攔截時,JavaScript會立刻跳去執行catch
區塊內的敘述。catch
關鍵字後必須用小括號()
來填入自訂的參數名稱,這個參數即從try
區塊中被拋出的錯誤訊息。若try
區塊並沒有拋出錯誤訊息,catch
區塊的程式敘述就不會被執行。
例如:
console.log("Hello");
try {
throw "I am an error!";
console.log("This can't be executed.");
} catch (err) {
console.log(err);
}
console.log("World");
以上程式的輸出結果如下:
I am an error!
World
雖然TypeScript有蠻強大的型別功能,但對於catch
關鍵字後小括號()
中定義的參數名稱,我們完全沒有辦法去限制它的型別。換句話說,這個參數的型別永遠會被TypeScript當作any
。
try-catch-finally
我們可以在catch
區塊後再使用finally
關鍵字來撰寫當try
區塊或是catch
區塊執行完之後要執行的程式區塊。
例如:
console.log("Hello");
try {
throw "I am an error!";
console.log("This can't be executed.");
} catch (err) {
console.log(err);
} finally {
console.log("Done!");
}
console.log("World");
以上程式的輸出結果如下:
I am an error!
Done!
World
專門用來「被拋出」的物件
雖然TypeScript的throw
關鍵字可以拋出任意值,但我們還是應該要儘量使用JavaScript內建的專門用來「被拋出」的物件。
RangeError
RangeError
物件可用來表示輸入的數值超出範圍。
例如:
try {
const n = 1.2345;
console.log(n.toFixed(1000));
} catch (err) {
console.log(err instanceof RangeError);
}
以上程式會輸出true
,因為數值的toFixed
方法的第一個參數只能是0 ~ 20的數值,若為負數或是超過20
(或超過20
太多),就會拋出RangeError
物件。
我們也可以自己「new」一個RangeError
物件來拋出,RangeError
的建構子可傳入一個字串作為錯誤描述。如下:
function discount(price: number, percentage: number): number {
if (percentage >= 0 && percentage <= 100) {
return price * (1 - percentage / 100);
} else {
throw new RangeError("The form of the percentage needs to be from 0 to 100.");
}
}
try {
console.log(discount(100, 20));
console.log(discount(100, 999));
} catch (err) {
console.log(err);
}
以上程式的輸出結果如下:
RangeError: The form of the percentage needs to be from 0 to 100. at discount (index.js:7:15) at Object.<anonymous> (index.js:12:17) at Module._compile (internal/modules/cjs/loader.js:774:30) at Object.Module._extensions..js (internal/modules/cjs/loader.js:785:10) at Module.load (internal/modules/cjs/loader.js:641:32) at Function.Module._load (internal/modules/cjs/loader.js:556:12) at Function.Module.runMain (internal/modules/cjs/loader.js:837:10) at internal/main/run_main_module.js:17:11
相信您應該發現了,RangeError
物件除了有輸出我們傳入其建構子作為參數的The form of the percentage needs to be from 0 to 100.
字串外,還有輸出RangeError
物件被拋出的位置,以及相關的堆疊解開(stack unwinding)資訊。(只不過這邊輸出的位置是指JavaScript程式的位置,並不是TypeScript程式。)
這就是為什麼前面會提到,throw
關鍵字應該要儘量使用JavaScript內建的專門用來「被拋出」的物件啦!用這樣的物件才能偵錯嘛~
ReferenceError
當我們在JavaScript中使用JavaScript不認識的名稱時,就會拋出這個物件。當然,因為我們是用TypeScript來開發JavaScript程式,這個狀況並不會很常遇到。
例如:
declare const n: number;
try {
console.log(n);
} catch (err) {
console.log(err instanceof ReferenceError);
}
以上程式會輸出true
,因為程式第一行只是在TypeScript中「假宣告」出n
這個常數。所以程式第四行要存取n
時會拋出ReferenceError
物件。
SyntaxError
JavaScript內建的eval
函數有一個參數,可以將JavaScript程式碼作為字串傳入,如此一來就能夠在程式執行階段動態地產生並執行JavaScript程式碼。
例如:
try {
const result = eval(`
function addOne(x) {
return x + 1;
}
addOne(1);
`);
console.log(result);
} catch (err) {
console.log(err instanceof SyntaxError);
}
以上程式會輸出2
。
當傳進eval
函數的字串內的JavaScript的語法有錯誤時,就會拋出這個例外。
例如:
try {
const result = eval(`
function addOne(x) {
return x + 1;
addOne(1);
`);
console.log(result);
} catch (err) {
console.log(err instanceof SyntaxError);
}
以上程式會輸出true
,因為我們把addOne
函數的結尾大括號}
刪除了。
TypeError
假如f
是一個函數,則f()
敘述可以呼叫它,但如果f
不是一個函數,此時用f()
嘗試呼叫它的話,就會拋出TypeError
物件;假如T
是一個類別或是函數,則new T()
敘述可以建立其物件實體,但如果T
不是一個類別或是函數,此時用new T()
敘述嘗試建立其物件實體的話,就會拋出TypeError
物件。當然,因為我們是用TypeScript來開發JavaScript程式,這個狀況並不會很常遇到。
例如:
const T = {
f1: 1,
f2: "2",
};
try {
console.log(new (T as any)());
} catch (err) {
console.log(err instanceof TypeError);
}
以上程式會輸出true
,因為物件T
只是大括號物件,無法直接被「new」出物件實體。
URIError
JavaScript內建的encodeURI
和decodeURI
函數可以用來安全地編碼與解碼URI。
用法如下:
const originalURL = "https://magiclen.org/中文";
const encodedURL = encodeURI(originalURL);
console.log(encodedURL);
const decodedURL = decodeURI(encodedURL);
console.log(decodedURL);
以上程式的輸出結果如下:
https://magiclen.org/中文
如果只是要編碼或解碼URI的其中一個部件(component),可以使用JavaScript內建的encodeURIComponent
或decodeURIComponent
函數。
用法如下:
const originalURLComponent = "中文";
const encodedURLComponent = encodeURIComponent(originalURLComponent);
console.log(encodedURLComponent);
const decodedURLComponent = decodeURIComponent(encodedURLComponent);
console.log(decodedURLComponent);
以上程式的輸出結果如下:
中文
在使用encodeURI
、decodeURI
、encodeURIComponent
或decodeURIComponent
函數時,若輸入的參數有誤(非正確格式的URI),就會拋出URIError
物件。
例如:
try {
console.log(encodeURI("\uD800"));
} catch (err) {
console.log(err instanceof URIError);
}
try {
console.log(decodeURI("%"));
} catch (err) {
console.log(err instanceof URIError);
}
try {
console.log(encodeURIComponent("\uD800"));
} catch (err) {
console.log(err instanceof URIError);
}
try {
console.log(decodeURIComponent("%"));
} catch (err) {
console.log(err instanceof URIError);
}
以上程式的輸出結果如下:
true
true
true
雖然不是很重要,但還是提一下。JavaScript無法編碼含有\uD800
~ \uDFFF
字元的字串。
而\u
是JavaScript的字元表示方式,其後可以接上2個位元組長度的16進制數值,作為這個字元的字元值。另外還有\x
,可以接上1個位元組長度的16進制數值。
Error
上面介紹的RangeError
、ReferenceError
、SyntaxError
、TypeError
、URIError
都繼承自Error
。我們可以把任何不屬於上述錯誤的錯誤都用Error
物件來表示。
Error
的建構子可傳入一個字串作為錯誤描述。如下:
try {
throw new Error("Something wrong!");
} catch (err) {
console.log(err);
}
以上程式會輸出:
拋出Error
相關物件時不使用new
關鍵字
RangeError
、ReferenceError
、SyntaxError
、TypeError
、URIError
和Error
除了可以被「new」出物件實體之外,也可以直接用呼叫函數的方式來讓它們回傳自己的物件實體。
例如:
try {
throw new Error("Something wrong!");
} catch (err) {
console.log(err);
}
可以改寫成:
try {
throw Error("Something wrong!");
} catch (err) {
console.log(err);
}
這樣就可以少打幾個字啦!
總結
在這個章節中,我們學會了TypeScript程式的錯誤處理方式。下一章要來介紹ES6之後加入的神器──產生器(Generator)。
下一章:迭代器(Iterator)與產生器(Generator)。