來源: ApacheCN『JavaScript 編程精解 中文第三版』翻譯項目原文:Bugs and Errorsjavascript
譯者:飛龍html
協議:CC BY-NC-SA 4.0java
自豪地採用谷歌翻譯git
部分參考了《JavaScript 編程精解(第 2 版)》程序員
調試的難度是開始編寫代碼的兩倍。 所以,若是你儘量巧妙地編寫代碼,那麼根據定義,你的智慧不足以進行調試。github
Brian Kernighan 和 P.J. Plauger,《The Elements of Programming Style》typescript
計算機程序中的缺陷一般稱爲 bug。 它讓程序員以爲很好,將它們想象成小事,只是碰巧進入咱們的做品。 實際上,固然,咱們本身把它們放在了那裏。apache
若是一個程序是思想的結晶,你能夠粗略地將錯誤分爲由於思想混亂引發的錯誤,以及思想轉換爲代碼時引入的錯誤。 前者一般比後者更難診斷和修復。編程
計算機可以自動地向咱們指出許多錯誤,若是它足夠了解咱們正在嘗試作什麼。 可是這裏 JavaScript 的寬鬆是一個障礙。 它的綁定和屬性概念很模糊,在實際運行程序以前不多會發現拼寫錯誤。 即便這樣,它也容許你作一些不會報錯的無心義的事情,好比計算true *'monkey'
。數組
JavaScript 有一些報錯的事情。 編寫不符合語言語法的程序會當即使計算機報錯。 其餘的東西,好比調用不是函數的東西,或者在未定義的值上查找屬性,會致使在程序嘗試執行操做時報告錯誤。
不過,JavaScript 在處理無心義的計算時,會僅僅返回NaN
(表示不是數字)或undefined
這樣的結果。程序會認爲其執行的代碼毫無問題並順利運行下去,要等到隨後的運行過程當中纔會出現問題,而此時已經有許多函數使用了這個無心義的值。程序執行中也可能不會遇到任何錯誤,只會產生錯誤的程序輸出。找出這類錯誤的源頭是很是困難的。
咱們將查找程序中的錯誤或者 bug 的過程稱爲調試(debug)。
當啓用了嚴格模式(strict mode)後,JavaScript 就會在執行代碼時變得更爲嚴格。咱們只需在文件或函數體頂部放置字符串"use strict"
就能夠啓用嚴格模式了。下面是示例代碼:
function canYouSpotTheProblem() { "use strict"; for (counter = 0; counter < 10; counter++) { console.log("Happy happy"); } } canYouSpotTheProblem(); // → ReferenceError: counter is not defined
一般,當你忘記在綁定前面放置let
時,就像在示例中的counter
同樣,JavaScript 靜靜地建立一個全局綁定並使用它。 在嚴格模式下,它會報告錯誤。 這很是有幫助。 可是,應該指出的是,當綁定已經做爲全局綁定存在時,這是行不通的。 在這種狀況下,循環仍然會悄悄地覆蓋綁定的值。
嚴格模式中的另外一個變化是,在未被做爲方法而調用的函數中,this
綁定持有值undefined
。 當在嚴格模式以外進行這樣的調用時,this
引用全局做用域對象,該對象的屬性是全局綁定。 所以,若是你在嚴格模式下不當心錯誤地調用方法或構造器,JavaScript 會在嘗試從this
讀取某些內容時產生錯誤,而不是愉快地寫入全局做用域。
例如,考慮下面的代碼,該代碼不帶new
關鍵字調用構造器,以便其this
不會引用新構造的對象:
function Person(name) { this.name = name; } let ferdinand = Person("Ferdinand"); // oops console.log(name); // → Ferdinand
雖然咱們錯誤調用了Person
,代碼也能夠執行成功,但會返回一個未定義值,並建立名爲name
的全局綁定。而在嚴格模式中,結果就不一樣了。
"use strict"; function Person(name) { this.name = name; } let ferdinand = Person("Ferdinand"); // → TypeError: Cannot set property 'name' of undefined
JavaScript 會當即告知咱們代碼中包含錯誤。這種特性十分有用。
幸運的是,使用class
符號建立的構造器,若是在不使用new
來調用,則始終會報錯,即便在非嚴格模式下也不會產生問題。
嚴格模式作了更多的事情。 它不容許使用同一名稱給函數賦多個參數,而且徹底刪除某些有問題的語言特性(例如with
語句,這是錯誤的,本書不會進一步討論)。
簡而言之,在程序頂部放置"use strict"
不多會有問題,而且可能會幫助你發現問題。
有些語言甚至在運行程序以前想要知道,全部綁定和表達式的類型。 當類型以不一致的方式使用時,他們會立刻告訴你。 JavaScript 只在實際運行程序時考慮類型,即便常常嘗試將值隱式轉換爲它預期的類型,因此它沒有多大幫助。
儘管如此,類型爲討論程序提供了一個有用的框架。 許多錯誤來自於值的類型的困惑,它們進入或來自一個函數。 若是你把這些信息寫下來,你不太可能會感到困惑。
你能夠在上一章的goalOrientedRobot
函數上面,添加一個像這樣的註釋來描述它的類型。
// (WorldState, Array) → {direction: string, memory: Array} function goalOrientedRobot(state, memory) { // ... }
有許多不一樣的約定,用於標註 JavaScript 程序的類型。
關於類型的一點是,他們須要引入本身的複雜性,以便可以描述足夠有用的代碼。 你認爲從數組中返回一個隨機元素的randomPick
函數的類型是什麼? 你須要引入一個綁定類型T
,它能夠表明任何類型,這樣你就能夠給予randomPick
一個像([T])->T
的類型(從T
到T
的數組的函數)。
當程序的類型已知時,計算機能夠爲你檢查它們,在程序運行以前指出錯誤。 有幾種 JavaScript 語言爲語言添加類型並檢查它們。 最流行的稱爲 TypeScript。 若是你有興趣爲你的程序添加更多的嚴謹性,我建議你嘗試一下。
在本書中,咱們將繼續使用原始的,危險的,非類型化的 JavaScript 代碼。
若是語言不會幫助咱們發現錯誤,咱們將不得不努力找到它們:經過運行程序並查看它是否正確執行。
一次又一次地手動操做,是一個很是糟糕的主意。 這不只使人討厭,並且也每每是無效的,由於每次改變時都須要花費太多時間來詳盡地測試全部內容。
計算機擅長重複性任務,測試是理想的重複性任務。 自動化測試是編寫測試另外一個程序的程序的過程。 編寫測試比手工測試有更多的工做,可是一旦你完成了它,你就會得到一種超能力:它只須要幾秒鐘就能夠驗證,你的程序在你編寫爲其測試的全部狀況下都能正常運行。 當你破壞某些東西時,你會當即注意到,而不是在稍後的時間裏隨機地碰到它。
測試一般採用小標籤程序的形式來驗證代碼的某些方面。 例如,一組(標準的,可能已經由其餘人測試過)toUpperCase
方法的測試可能以下:
function test(label, body) { if (!body()) console.log(`Failed: ${label}`); } test("convert Latin text to uppercase", () => { return "hello".toUpperCase() == "HELLO"; }); test("convert Greek text to uppercase", () => { return "Χαίρετε".toUpperCase() == "ΧΑΊΡΕΤΕ"; }); test("don't convert case-less characters", () => { return "مرحبا".toUpperCase() == "مرحبا"; });
像這樣寫測試每每會產生不少重複,笨拙的代碼。 幸運的是,有些軟件經過提供適合於表達測試的語言(以函數和方法的形式),並在測試失敗時輸出豐富的信息來幫助你構建和運行測試集合(測試套件,test suite)。 這些一般被稱爲測試運行器(test runner)。
一些代碼比其餘代碼更容易測試。 一般,代碼與外部交互的對象越多,創建用於測試它的上下文就越困難。 上一章中顯示的編程風格,使用自包含的持久值而不是更改對象,一般很容易測試。
當程序的運行結果不符合預期或在運行過程當中產生錯誤時,你就會注意到程序出現問題了,下一步就是要推斷問題出在什麼地方。
有時錯誤很明顯。錯誤消息會指出錯誤出如今程序的哪一行,只要稍加閱讀錯誤描述及出錯的那行代碼,你通常就知道如何修正錯誤了。
但不老是這樣。 有時觸發問題的行,只是第一個地方,它以無效方式使用其餘地方產生的奇怪的值。 若是你在前幾章中已經解決了練習,你可能已經遇到過這種狀況。
下面的示例代碼嘗試將一個整數轉換成給定進製表示的字符串(十進制、二進制等),其原理是:不斷循環取出最後一位數字,並將其除以基數(將最後一位數從數字中除去)。但該程序目前的輸出代表程序中是存在bug的。
function numberToString(n, base = 10) { let result = "", sign = ""; if (n < 0) { sign = "-"; n = -n; } do { result = String(n % base) + result; n /= base; } while (n > 0); return sign + result; } console.log(numberToString(13, 10)); // → 1.5e-3231.3e-3221.3e-3211.3e-3201.3e-3191.3e-3181.3…
你可能已經發現程序運行結果不對了,不過先暫時裝做不知道。咱們知道程序運行出了問題,試圖找出其緣由。
這是一個地方,你必須抵制隨機更改代碼來查看它是否變得更好的衝動。 相反,要思考。 分析正在發生的事情,並提出爲何可能發生的理論。 而後,再作一些觀察來檢驗這個理論 - 或者,若是你尚未理論,能夠進一步觀察來幫助你想出一個理論。
有目的地在程序中使用console.log
來查看程序當前的運行狀態,是一種不錯的獲取額外信息的方法。在本例中,咱們但願n
的值依次變爲 13,1,而後是 0。讓咱們先在循環起始處輸出n
的值。
13 1.3 0.13 0.013 … 1.5e-323
沒錯。13 除以 10 並不會產生整數。咱們不該該使用n/=base
,而應該使用n=Math.floor(n/base)
,使數字「右移」,這纔是咱們實際想要的結果。
使用console.log
來查看程序行爲的替代方法,是使用瀏覽器的調試器(debugger)功能。 瀏覽器能夠在代碼的特定行上設置斷點(breakpoint)。 當程序執行到帶有斷點的行時,它會暫停,而且你能夠檢查該點的綁定值。 我不會詳細討論,由於調試器在不一樣瀏覽器上有所不一樣,但請查看瀏覽器的開發人員工具或在 Web 上搜索來獲取更多信息。
設置斷點的另外一種方法,是在程序中包含一個debugger
語句(僅由該關鍵字組成)。 若是你的瀏覽器的開發人員工具是激活的,則只要程序達到這個語句,程序就會暫停。
不幸的是,程序員不可能避免全部問題。 若是你的程序以任何方式與外部世界進行通訊,則可能會致使輸入格式錯誤,工做負荷太重或網絡故障。
若是你只爲本身編程,那麼你就能夠忽略這些問題直到它們發生。 可是若是你建立了一些將被其餘人使用的東西,你一般但願程序比只是崩潰作得更好。 有時候,正確的作法是不擇手段地繼續運行。 在其餘狀況下,最好向用戶報告出了什麼問題而後放棄。 但不管在哪一種狀況下,該程序都必須積極採起措施來回應問題。
假設你有一個函數promptInteger
,要求用戶輸入一個整數並返回它。 若是用戶輸入"orange"
,它應該返回什麼?
一種辦法是返回一個特殊值,一般會使用null
,undefined
或 -1。
function promptNumber(question) { let result = Number(prompt(question, "")); if (Number.isNaN(result)) return null; else return result; } console.log(promptNumber("How many trees do you see?"));
如今,調用promptNumber
的任何代碼都必須檢查是否實際讀取了數字,不然必須以某種方式恢復 - 也許再次詢問或填充默認值。 或者它可能會再次向它的調用者返回一個特殊值,表示它未能完成所要求的操做。
在不少狀況下,當錯誤很常見而且調用者應該明確地考慮它們時,返回特殊值是表示錯誤的好方法。 但它確實有其不利之處。 首先,若是函數已經可能返回每一種可能的值呢? 在這樣的函數中,你必須作一些事情,好比將結果包裝在一個對象中,以便可以區分紅功與失敗。
function lastElement(array) { if (array.length == 0) { return {failed: true}; } else { return {element: array[array.length - 1]}; } }
返回特殊值的第二個問題是它可能產生很是笨拙的代碼。 若是一段代碼調用promptNumber
10 次,則必須檢查是否返回null
10 次。 若是它對null
的迴應是簡單地返回null
自己,函數的調用者將不得不去檢查它,以此類推。
當函數沒法正常工做時,咱們只但願中止當前任務,並當即跳轉到負責處理問題的位置。這就是異常處理的功能。
異常是一種當代碼執行中遇到問題時,能夠觸發(或拋出)異常的機制,異常只是一個普通的值。觸發異常相似於從函數中強制返回:異常不僅跳出到當前函數中,還會跳出函數調用方,直到當前執行流初次調用函數的位置。這種方式被稱爲「堆棧展開(Unwinding the Stack)」。你可能還記得咱們在第3章中介紹的函數調用棧,異常會減少堆棧的尺寸,並丟棄全部在縮減程序棧尺寸過程當中遇到的函數調用上下文。
若是異常老是會將堆棧尺寸縮減到棧底,那麼異常也就毫無用處了。它只不過是換了一種方式來完全破壞你的程序罷了。異常真正強大的地方在於你能夠在堆棧上設置一個「障礙物」,當異常縮減堆棧到達這個位置時會被捕獲。一旦發現異常,你可使用它來解決問題,而後繼續運行該程序。
function promptDirection(question) { let result = prompt(question, ""); if (result.toLowerCase() == "left") return "L"; if (result.toLowerCase() == "right") return "R"; throw new Error("Invalid direction: " + result); } function look() { if (promptDirection("Which way?") == "L") { return "a house"; } else { return "two angry bears"; } } try { console.log("You see", look()); } catch (error) { console.log("Something went wrong: " + error); }
throw
關鍵字用於引起異常。 異常的捕獲經過將一段代碼包裝在一個try
塊中,後跟關鍵字catch
來完成。 當try
塊中的代碼引起異常時,將求值catch
塊,並將括號中的名稱綁定到異常值。 在catch
塊結束以後,或者try
塊結束而且沒有問題時,程序在整個try / catch
語句的下面繼續執行。
在本例中,咱們使用Error
構造器來建立異常值。這是一個標準的 JavaScript 構造器,用於建立一個對象,包含message
屬性。在多數 JavaScript 環境中,構造器實例也會收集異常建立時的調用棧信息,即堆棧跟蹤信息(Stack Trace)。該信息存儲在stack
屬性中,對於調用問題有很大的幫助,咱們能夠從堆棧跟蹤信息中得知問題發生的精確位置,即問題具體出如今哪一個函數中,以及執行失敗爲止調用的其餘函數鏈。
須要注意的是如今look
函數能夠徹底忽略promptDirection
出錯的可能性。這就是使用異常的優點:只有在錯誤觸發且必須處理的位置才須要錯誤處理代碼。其間的函數能夠忽略異常處理。
嗯,咱們要講解的理論知識差很少就這些了。
異常的效果是另外一種控制流。 每一個可能致使異常的操做(幾乎每一個函數調用和屬性訪問)均可能致使控制流忽然離開你的代碼。
這意味着當代碼有幾個反作用時,即便它的「常規」控制流看起來像它們老是會發生,但異常可能會阻止其中一些發生。
這是一些很是糟糕的銀行代碼。
const accounts = { a: 100, b: 0, c: 20 }; function getAccount() { let accountName = prompt("Enter an account name"); if (!accounts.hasOwnProperty(accountName)) { throw new Error(`No such account: ${accountName}`); } return accountName; } function transfer(from, amount) { if (accounts[from] < amount) return; accounts[from] -= amount; accounts[getAccount()] += amount; }
transfer
函數將一筆錢從一個給定的帳戶轉移到另外一個帳戶,在此過程當中詢問另外一個帳戶的名稱。 若是給定一個無效的賬戶名稱,getAccount
將引起異常。
可是transfer
首先從賬戶中刪除資金,以後調用getAccount
,以後將其添加到另外一個賬戶。 若是它在那個時候由異常中斷,它就會讓錢消失。
這段代碼原本能夠更智能一些,例如在開始轉移資金以前調用getAccount
。 但這樣的問題每每以更微妙的方式出現。 即便是那些看起來不像是會拋出異常的函數,在特殊狀況下,或者當他們包含程序員的錯誤時,也可能會這樣。
解決這個問題的一個方法是使用更少的反作用。 一樣,計算新值而不是改變現有數據的編程風格有所幫助。 若是一段代碼在建立新值時中止運行,沒有人會看到這個完成一半的值,而且沒有問題。
但這並不老是實際的。 因此try
語句具備另外一個特性。 他們可能會跟着一個finally
塊,而不是catch
塊,也不是在它後面。 finally
塊會說「無論發生什麼事,在嘗試運行try
塊中的代碼後,必定會運行這個代碼。」
function transfer(from, amount) { if (accounts[from] < amount) return; let progress = 0; try { accounts[from] -= amount; progress = 1; accounts[getAccount()] += amount; progress = 2; } finally { if (progress == 1) { accounts[from] += amount; } } }
這個版本的函數跟蹤其進度,若是它在離開時注意到,它停止在建立不一致的程序狀態的位置,則修復它形成的損害。
請注意,即便finally
代碼在異常退出try
塊時運行,它也不會影響異常。finally
塊運行後,堆棧繼續展開。
即便異常出如今意外的地方,編寫可靠運行的程序也很是困難。 不少人根本就不關心,並且因爲異常一般針對異常狀況而保留,所以問題可能不多發生,甚至從未被發現。 這是一件好事仍是一件糟糕的事情,取決於軟件執行失敗時會形成多大的損害。
當程序出現異常且異常未被捕獲時,異常就會直接回退到棧頂,並由 JavaScript 環境來處理。其處理方式會根據環境的不一樣而不一樣。在瀏覽器中,錯誤描述一般會寫入 JavaScript 控制檯中(可使用瀏覽器工具或開發者菜單來訪問控制檯)。咱們將在第 20 章中討論的,無瀏覽器的 JavaScript 環境 Node.js 對數據損壞更加謹慎。 當發生未處理的異常時,它會停止整個過程。
對於程序員的錯誤,讓錯誤通行一般是最好的。 未處理的異常是表示糟糕的程序的合理方式,而在現代瀏覽器上,JavaScript 控制檯爲你提供了一些信息,有關在發生問題時堆棧上調用了哪些函數的。
對於在平常使用中發生的預期問題,因未處理的異常而崩潰是一種糟糕的策略。
語言的非法使用方式,好比引用一個不存在的綁定,在null中查詢屬性,或調用的對象不是函數最終都會引起異常。你能夠像本身的異常同樣捕獲這些異常。
進入catch
語句塊時,咱們只知道try
體中引起了異常,但不知道引起了哪一類或哪個異常。
JavaScript(很明顯的疏漏)並未對選擇性捕獲異常提供良好的支持,要不捕獲全部異常,要不什麼都不捕獲。這讓你很容易假設,你獲得的異常就是你在寫catch
時所考慮的異常。
但它也可能不是。 可能會違反其餘假設,或者你可能引入了致使異常的 bug。 這是一個例子,它嘗試持續調用promptDirection
,直到它獲得一個有效的答案:
for (;;) { try { let dir = promtDirection("Where?"); // ← typo! console.log("You chose ", dir); break; } catch (e) { console.log("Not a valid direction. Try again."); } }
咱們可使用for (;;)
循環體來建立一個無限循環,其自身永遠不會中止運行。咱們在用戶給出有效的方向以後會跳出循環。但咱們拼寫錯了promptDirection
,所以會引起一個「未定義值」錯誤。因爲catch
塊徹底忽略了異常值,假定其知道問題所在,錯將綁定錯誤信息當成錯誤輸入。這樣不只會引起無限循環,並且會掩蓋掉真正的錯誤消息——綁定名拼寫錯誤。
通常而言,只有將拋出的異常重定位到其餘地方進行處理時,咱們纔會捕獲全部異常。好比說經過網絡傳輸通知其餘系統當前應用程序的崩潰信息。即使如此,咱們也要注意編寫的代碼是否會將錯誤信息掩蓋起來。
所以,咱們轉而會去捕獲那些特殊類型的異常。咱們能夠在catch
代碼塊中判斷捕獲到的異常是否就是咱們指望處理的異常,若是不是則將其從新拋出。那麼咱們該如何辨別拋出異常的類型呢?
咱們能夠將它的message
屬性與咱們所指望的錯誤信息進行比較。 可是,這是一種不穩定的編寫代碼的方式 - 咱們將使用供人類使用的信息來作出程序化決策。 只要有人更改(或翻譯)該消息,代碼就會中止工做。
咱們不如定義一個新的錯誤類型,並使用instanceof
來識別異常。
class InputError extends Error {} function promptDirection(question) { let result = prompt(question); if (result.toLowerCase() == "left") return "L"; if (result.toLowerCase() == "right") return "R"; throw new InputError("Invalid direction: " + result); }
新的錯誤類擴展了Error
。 它沒有定義它本身的構造器,這意味着它繼承了Error
構造器,它須要一個字符串消息做爲參數。 事實上,它根本沒有定義任何東西 - 這個類是空的。 InputError
對象的行爲與Error
對象類似,只是它們的類不一樣,咱們能夠經過類來識別它們。
如今循環能夠更仔細地捕捉它們。
for (;;) { try { let dir = promptDirection("Where?"); console.log("You chose ", dir); break; } catch (e) { if (e instanceof InputError) { console.log("Not a valid direction. Try again."); } else { throw e; } } }
這裏的catch
代碼只會捕獲InputError
類型的異常,而其餘類型的異常則不會在這裏進行處理。若是又輸入了不正確的值,那麼系統會向用戶準確報告錯誤——「綁定未定義」。
斷言(assertions)是程序內部的檢查,用於驗證某個東西是它應該是的方式。 它們並非用於處理正常操做中可能出現的狀況,而是發現程序員的錯誤。
例如,若是firstElement
被描述爲一個函數,永遠不會在空數組上調用,咱們能夠這樣寫:
function firstElement(array) { if (array.length == 0) { throw new Error("firstElement called with []"); } return array[0]; }
如今,它不會默默地返回未定義值(當你讀取一個不存在的數組屬性的時候),而是在你濫用它時當即幹掉你的程序。 這使得這種錯誤不太可能被忽視,而且當它們發生時更容易找到它們的緣由。
我不建議嘗試爲每種可能的不良輸入編寫斷言。 這將是不少工做,並會產生很是雜亂的代碼。 你會但願爲很容易犯(或者你發現本身作過)的錯誤保留他們。
錯誤和無效的輸入十分常見。編程的一個重要部分是發現,診斷和修復錯誤。 若是你擁有自動化測試套件或向程序添加斷言,則問題會變得更容易被注意。
咱們經常須要使用優雅的方式來處理程序可控範圍外的問題。若是問題能夠就地解決,那麼返回一個特殊的值來跟蹤錯誤就是一個不錯的解決方案。或者,異常也多是可行的。
拋出異常會引起堆棧展開,直到遇到下一個封閉的try/catch
塊,或堆棧底部爲止。catch
塊捕獲異常後,會將異常值賦予catch
塊,catch
塊中應該驗證異常是不是實際但願處理的異常,而後進行處理。爲了有助於解決因爲異常引發的不可預測的執行流,可使用finally
塊來確保執行try
塊以後的代碼。
假設有一個函數primitiveMultiply
,在 20% 的狀況下將兩個數相乘,在另外 80% 的狀況下會觸發MultiplicatorUnitFailure
類型的異常。編寫一個函數,調用這個容易出錯的函數,不斷嘗試直到調用成功並返回結果爲止。
確保只處理你指望的異常。
class MultiplicatorUnitFailure extends Error {} function primitiveMultiply(a, b) { if (Math.random() < 0.2) { return a * b; } else { throw new MultiplicatorUnitFailure(); } } function reliableMultiply(a, b) { // Your code here. } console.log(reliableMultiply(8, 8)); // → 64
考慮如下這個編寫好的對象:
const box = { locked: true, unlock() { this.locked = false; }, lock() { this.locked = true; }, _content: [], get content() { if (this.locked) throw new Error("Locked!"); return this._content; } };
這是一個帶鎖的箱子。其中有一個數組,但只有在箱子被解鎖時,才能夠訪問數組。不容許直接訪問_content
屬性。
編寫一個名爲withBoxUnlocked
的函數,接受一個函數類型的參數,其做用是解鎖箱子,執行該函數,不管是正常返回仍是拋出異常,在withBoxUnlocked
函數返回前都必須鎖上箱子。
const box = { locked: true, unlock() { this.locked = false; }, lock() { this.locked = true; }, _content: [], get content() { if (this.locked) throw new Error("Locked!"); return this._content; } }; function withBoxUnlocked(body) { // Your code here. } withBoxUnlocked(function() { box.content.push("gold piece"); }); try { withBoxUnlocked(function() { throw new Error("Pirates on the horizon! Abort!"); }); } catch (e) { console.log("Error raised:", e); } console.log(box.locked); // → true