在咱们的程序中,事情并不是一路顺风。html
特别是在某些状况下,咱们可能但愿在中止程序或在发生不良情况时通知用户。
例如:node
在全部的这些状况下,咱们做为程序员都会产生错误,或者让编程引擎为咱们建立一些错误。程序员
在建立错误以后,咱们能够向用户通知消息,或者能够彻底中止执行。编程
JavaScript 中的错误是一个对象,随后被抛出,用以终止程序。json
要在 JavaScript 中建立新错误,咱们调用相应的构造函数。例如,要建立一个新的通用错误,能够执行如下操做:api
const err = new Error("Something bad happened!");
建立错误对象时,也能够省略关键字 new
:数组
const err = Error("Something bad happened!");
建立后,错误对象将显示三个属性:promise
message
:带有错误信息的字符串。name
:错误的类型。stack
:函数执行的栈跟踪。例如,若是咱们用适当的消息建立一个新的 TypeError
对象,则 message
将携带实际的错误字符串,而 name
则为 TypeError
:浏览器
const wrongType = TypeError("Wrong type given, expected number"); wrongType.message; // "Wrong type given, expected number" wrongType.name; // "TypeError"
Firefox 还实现了一堆非标准属性,例如 columnNumber
,filename
和 lineNumber
。安全
JavaScript 中有不少类型的错误,即:
Error
EvalError
InternalError
RangeError
ReferenceError
SyntaxError
TypeError
URIError
请记住,全部这些错误类型都是实际构造函数,旨在返回一个新的错误对象。
在代码中主要用 Error
和 TypeError
这两种最多见的类型来建立本身的错误对象。
可是在大多数状况下,不少错误直接来自 JavaScript 引擎,例如 InternalError
或 SyntaxError
。
下面的例子是当你尝试从新为 const
赋值时,将触发 TypeError
:
const name = "Jules"; name = "Caty"; // TypeError: Assignment to constant variable.
当你关键字拼错时,就会触发 SyntaxError
:
va x = '33'; // SyntaxError: Unexpected identifier
或者,当你在错误的地方使用保留关键字时,例如在 async
函数以外的使用 await
:
function wrong(){ await 99; } wrong(); // SyntaxError: await is only valid in async function
当在页面中选择不存在的 HTML 元素时,会发生 TypeError
:
Uncaught TypeError: button is null
除了这些“传统的”错误对象外,AggregateError
对象也即将可以在 JavaScript 中使用。
AggregateError
能够把多个错误很方便地包装在一块儿,在后面将会看到。
除了这些内置错误外,在浏览器中还能够找到:
DOMException
DOMError
已弃用,目前再也不使用。DOMException
是与 Web API 相关的一系列错误。有关完整列表,请参见 MDN。
不少人认为错误和异常是一回事。实际上错误对象仅在抛出时才成为异常。
要在 JavaScript 中引起异常,咱们使用 throw
关键字,后面跟错误对象:
const wrongType = TypeError("Wrong type given, expected number"); throw wrongType;
更常见的是缩写形式,在大多数代码库中,你均可以找到:
throw TypeError("Wrong type given, expected number");
或者:
throw new TypeError("Wrong type given, expected number");
通常不会把异常抛出到函数或条件块以外,固然也有例外状况,例如:
function toUppercase(string) { if (typeof string !== "string") { throw TypeError("Wrong type given, expected a string"); } return string.toUpperCase(); }
在代码中咱们检查函数的参数是否为字符串,若是不是则抛出异常。
从技术上讲,你能够在 JavaScript 中抛出任何东西,而不只仅是错误对象:
throw Symbol(); throw 33; throw "Error!"; throw null;
可是,最好不要这样作,应该老是抛出正确的错误对象,而不是原始类型。
这样就能够经过代码库保持错误处理的一致性。其余团队成员老是可以在错误对象上访问 error.message
或 error.stack
。
异常就像电梯在上升:一旦抛出一个异常,它就会在程序栈中冒泡,除非被卡在某个地方。
看下面的代码:
function toUppercase(string) { if (typeof string !== "string") { throw TypeError("Wrong type given, expected a string"); } return string.toUpperCase(); } toUppercase(4);
若是你在浏览器或 Node.js 中运行这段代码,程序将中止并报告错误:
Uncaught TypeError: Wrong type given, expected a string toUppercase http://localhost:5000/index.js:3 <anonymous> http://localhost:5000/index.js:9
另外还能够看到发生错误的代码行数。
这段报告是 栈跟踪(stack trace),对于跟踪代码中的问题颇有帮助。
栈跟踪从底部到顶部。因此是这样的:
toUppercase http://localhost:5000/index.js:3 <anonymous> http://localhost:5000/index.js:9
咱们能够说:
toUppercase
的内容toUppercase
在第 3 行引起了一个问题除了在浏览器的控制台中看到栈跟踪以外,还能够在错误对象的 stack
属性上对其进行访问。
若是异常是未捕获的,也就是说程序员没有采起任何措施来捕获它,则程序将会崩溃。
你在何时及在什么地方捕获代码中的异常取决于特定的用例。
例如,你可能想要在栈中传播异常,使程序彻底崩溃。当发生致命的错误,须要更安全地中止程序而不是处理无效数据时,你可能须要这样作。
介绍了基础知识以后,如今让咱们将注意力转向同步和异步 JavaScript 代码中的错误和异常处理。
同步代码一般很简单,它的错误处理也是如此。
同步代码按照代码顺序循序渐进的执行。让咱们再来看前面的例子:
function toUppercase(string) { if (typeof string !== "string") { throw TypeError("Wrong type given, expected a string"); } return string.toUpperCase(); } toUppercase(4);
在这里,引擎调用并执行 toUppercase
。全部操做都同步进行。要捕获由这种同步函数产生的异常,能够用 try/catch/finally
:
try { toUppercase(4); } catch (error) { console.error(error.message); // or log remotely } finally { // clean up }
一般try
处理主处理流程或者可能引起异常的函数调用。
而catch
则捕获实际的异常。它接收错误对象,能够在这里对其进行检查(并远程发送到生产环境中的日志服务器)。
另外不管函数的执行结果如何,无论是成功仍是失败,finally
中的全部代码都会被执行。
请记住: try/catch/finally
是一个同步结构:它能够捕获来自异步代码的异常。
JavaScript 中的生成器函数是一种特殊的函数。
除了在其内部做用域和使用者之间提供双向通讯通道以外,它还能够随意暂停和恢复。
要建立一个生成器函数,须要在关键字 function
以后加一个星号 *
:
function* generate() { // }
进入函数后,可使用 yield
返回值:
function* generate() { yield 33; yield 99; }
生成器函数的返回值是迭代器对象。有两种方法从生成器中提取值:
next()
。for...of
.for ... of
的迭代。以上面的代码为例,要从生成器获取值,能够这样作:
function* generate() { yield 33; yield 99; } const go = generate();
当调用生成器函数时,go
成了咱们的迭代器对象。
如今能够调用go.nex()
来执行:
function* generate() { yield 33; yield 99; } const go = generate(); const firstStep = go.next().value; // 33 const secondStep = go.next().value; // 99
生成器也能够经过其余方式工做:它们能够接受调用者返回的值和异常。
除了 next()
外,从生成器返回的迭代器对象还有 throw()
方法。用这个方法,能够经过把异常注入到生成器来暂停程序:
function* generate() { yield 33; yield 99; } const go = generate(); const firstStep = go.next().value; // 33 go.throw(Error("Tired of iterating!")); const secondStep = go.next().value; // never reached
能够用 try/catch
(和 finally
,若是须要的话)将代码包装在生成器中来捕获这样的错误:
function* generate() { try { yield 33; yield 99; } catch (error) { console.error(error.message); } }
生成器函数还能够向外部抛出异常。捕获这些异常的机制与捕获同步异常的机制相同:try/catch/finally
。
下面是经过 for ... of
从外部使用的生成器函数的例子:
function* generate() { yield 33; yield 99; throw Error("Tired of iterating!"); } try { for (const value of generate()) { console.log(value); } } catch (error) { console.error(error.message); } /* Output: 33 99 Tired of iterating! */
代码中迭代 try
块内的主处理流程。若是发生任何异常,就用 catch
中止。
JavaScript 在本质上是同步的,是一种单线程语言。
诸如浏览器引擎之类的环境用许多 Web API 加强了 JavaScript,用来与外部系统进行交互并处理与 I/O 绑定的操做。
浏览器中的异步示例包括timeouts、events、Promise。
异步代码中的错误处理与同步代码不一样。
看一些例子:
在你开始学习 JavaScript 时,当学 try/catch/finally
以后,你可能会想把它们放在任何代码块中。
思考下面的代码段:
function failAfterOneSecond() { setTimeout(() => { throw Error("Something went wrong!"); }, 1000); }
这个函数将在大约 1 秒钟后被触发。那么处理这个异常的正确方式是什么?
下面的例子是无效的:
function failAfterOneSecond() { setTimeout(() => { throw Error("Something went wrong!"); }, 1000); } try { failAfterOneSecond(); } catch (error) { console.error(error.message); }
正如前面所说的,try/catch
是同步的。另外一方面,咱们有 setTimeout
,这是一个用于定时器的浏览器 API。
到传递给 setTimeout
的回调运行时,try/catch
已经“消失了”。程序将会崩溃,由于咱们没法捕获异常。
它们在两条不一样的轨道上行驶:
Track A: --> try/catch Track B: --> setTimeout --> callback --> throw
若是咱们不想使程序崩溃,为了正确处理错误,咱们必须把 try/catch
移动到 setTimeout
的回调中。
可是这在大多数状况下并无什么意义。Promises 的异步错误处理提供了更好的方式。
文档对象模型中的HTML节点链接到 EventTarget
,EventTarget
是浏览器中全部 event emitter 的共同祖先。
这意味着咱们能够侦听页面中任何 HTML 元素上的事件。Node.js 将在将来版本中支持 EventTarget
。
DOM 事件的错误处理机制遵循与异步 Web API 的相同方案。
看下面的例子:
const button = document.querySelector("button"); button.addEventListener("click", function() { throw Error("Can't touch this button!"); });
在这里,单击按钮后会当即引起异常,应该怎样捕获它?下面的方法不起做用,并且不会阻止程序崩溃:
const button = document.querySelector("button"); try { button.addEventListener("click", function() { throw Error("Can't touch this button!"); }); } catch (error) { console.error(error.message); }
与前面的带有 setTimeout
的例子同样,传递给 addEventListener
的任何回调均异步执行:
Track A: --> try/catch Track B: --> addEventListener --> callback --> throw
若是不想使程序崩溃,为了正确处理错误,必须把 try/catch
放在 addEventListener
的回调内。但这样作没有任何价值。与 setTimeout
同样,异步代码路径引起的异常从外部是没法捕获的,这将会使程序崩溃。
HTML 元素具备许多事件处理函数,例如 onclick
、onmouseenter
和 onchange
等。
还有 onerror
,可是它与 throw
没有什么关系。
每当像 <img>
标签或 <script>
之类的 HTML 元素遇到不存在的资源时,onerror
事件处理函数都会触发。
看下面的例子:
// omitted <body> <img src="nowhere-to-be-found.png" alt="So empty!"> </body> // omitted
当访问缺乏或不存在资源的 HTML 文档时,浏览器的控制台会输出如下错误:
GET http://localhost:5000/nowhere-to-be-found.png [HTTP/1.1 404 Not Found 3ms]
在 JavaScript 中,咱们有机会使用适当的事件处理程序来“捕获”这个错误:
const image = document.querySelector("img"); image.onerror = function(event) { console.log(event); };
或者用更好的方法:
const image = document.querySelector("img"); image.addEventListener("error", function(event) { console.log(event); });
此模式可用于加载替代资源来替换丢失的图像或脚本。
可是要记住:onerror
与 throw
或 try/catch
无关。
为了说明 Promise 的错误处理,咱们将 “Promise” 前面的一个例子。调整如下功能:
function toUppercase(string) { if (typeof string !== "string") { throw TypeError("Wrong type given, expected a string"); } return string.toUpperCase(); } toUppercase(4);
为了代替返回简单的字符串或异常,能够分别用 Promise.reject
和 Promise.resolve
处理错误和成功:
function toUppercase(string) { if (typeof string !== "string") { return Promise.reject(TypeError("Wrong type given, expected a string")); } const result = string.toUpperCase(); return Promise.resolve(result); }
从技术上讲,这段代码中没有异步的东西,可是它能很好地说明这一点。
如今函数已 “promise 化”,咱们能够经过 then
使用结果,并附加 catch
来处理被拒绝的Promise:
toUppercase(99) .then(result => result) .catch(error => console.error(error.message));
这段代码将会输出:
Wrong type given, expected a string
在 Promise 领域,catch
是用于处理错误的结构。
除了 catch
和 then
以外,还有 finally
,相似于 try/catch
中的 finally
。
相对于同步而言,Promise 的 finally
运行与 Promise 结果无关:
toUppercase(99) .then(result => result) .catch(error => console.error(error.message)) .finally(() => console.log("Run baby, run"));
切记,传递给 then/catch/finally
的任何回调都是由微任务队列异步处理的。微任务优先于宏任务,例如事件和计时器。
做为拒绝 Promise 的最佳方法,提供错误对象很方便:
Promise.reject(TypeError("Wrong type given, expected a string"));
这样,你能够经过代码库保持错误处理的一致性。其余团队成员老是能够指望访问 error.message
,更重要的是你能够检查栈跟踪。
除了 Promise.reject
以外,能够经过抛出异常来退出 Promise 链。
看下面的例子:
Promise.resolve("A string").then(value => { if (typeof value === "string") { throw TypeError("Expected a number!"); } });
咱们用一个字符串解决一个 Promise,而后当即用 throw
打破这个链。
为了阻止异常的传播,照常使用 catch
:
Promise.resolve("A string") .then(value => { if (typeof value === "string") { throw TypeError("Expected a number!"); } }) .catch(reason => console.log(reason.message));
这种模式在 fetch
中很常见,咱们在其中检查响应对象并查找错误:
fetch("https://example-dev/api/") .then(response => { if (!response.ok) { throw Error(response.statusText); } return response.json(); }) .then(json => console.log(json));
在这里能够用 catch
拦截异常。若是失败了,或者决定不去捕获它,则异常能够在栈中冒泡。
从本质上讲,这还不错,可是在不一样的环境下对未捕获的 rejection 的反应不一样。
例如,未来的 Node.js 将使任何未处理 Promise rejection 的程序崩溃:
DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.
更好地捕获他们!
使用计时器或事件没法捕获从回调引起的异常。咱们在上一节中看到了例子:
function failAfterOneSecond() { setTimeout(() => { throw Error("Something went wrong!"); }, 1000); } // DOES NOT WORK try { failAfterOneSecond(); } catch (error) { console.error(error.message); }
Promise 提供的解决方案在于代码的“promisification”。基本上,咱们用 Promise 包装计时器:
function failAfterOneSecond() { return new Promise((_, reject) => { setTimeout(() => { reject(Error("Something went wrong!")); }, 1000); }); }
经过 reject
,咱们启动了Promise rejection,它带有一个错误对象。
这时能够用 catch
处理异常:
failAfterOneSecond().catch(reason => console.error(reason.message));
注意:一般使用 value
做为 Promise 的返回值,并用 reason
做为 rejection 的返回对象。
Node.js 有一个名为promisify的工具函数,能够简化旧式回调 API 的“混杂”。
静态方法 Promise.all
接受一个 Promise 数组,并返回全部解析 Promise 的结果数组:
const promise1 = Promise.resolve("All good!"); const promise2 = Promise.resolve("All good here too!"); Promise.all([promise1, promise2]).then((results) => console.log(results)); // [ 'All good!', 'All good here too!' ]
若是这些 Promise 中的任何一个被拒绝,Promise.all
都会拒绝,并返回第一个被拒绝的 Promise 中的错误。
为了在 Promise.all
中处理这些状况,须要使用 catch
,就像在上一节中所作的那样:
const promise1 = Promise.resolve("All good!"); const promise2 = Promise.reject(Error("No good, sorry!")); const promise3 = Promise.reject(Error("Bad day ...")); Promise.all([promise1, promise2, promise3]) .then(results => console.log(results)) .catch(error => console.error(error.message));
要再次运行函数而不考虑 Promise.all
的结果,咱们可使用 finally
。
Promise.all([promise1, promise2, promise3]) .then(results => console.log(results)) .catch(error => console.error(error.message)) .finally(() => console.log("Always runs!"));
咱们能够将 Promise.any
(Firefox> 79,Chrome> 85)视为与 Promise.all
相反。
即便数组中的一个 Promise 拒绝,Promise.all
也会返回失败,而 Promise.any
老是提供第一个已解决的Promise(若是存在于数组中),不管发生了什么拒绝。
若是传递给 Promise.any
的 Promise 不是都被拒绝,则产生的错误是 AggregateError
。考虑如下示例:
const promise1 = Promise.reject(Error("No good, sorry!")); const promise2 = Promise.reject(Error("Bad day ...")); Promise.any([promise1, promise2]) .then(result => console.log(result)) .catch(error => console.error(error)) .finally(() => console.log("Always runs!"));
这里用 catch
处理错误。这里代码的输出是:
AggregateError: No Promise in Promise.any was resolved Always runs!
AggregateError
对象有与基本 Error
相同的属性,以及 Errors
属性:
// .catch(error => console.error(error.errors)) //
这个属性是拒绝产生的每一个错误的数组:
[Error: "No good, sorry!, Error: "Bad day ..."]
静态方法 Promise.race
接受一个 Promise 数组:
const promise1 = Promise.resolve("The first!"); const promise2 = Promise.resolve("The second!"); Promise.race([promise1, promise2]).then(result => console.log(result)); // The first!
结果是第一个赢得“race”的 Promise。
那 rejection 呢?若是拒绝的 Promise 不是第一个出如今输入数组中的对象,则 Promise.race
解析:
const promise1 = Promise.resolve("The first!"); const rejection = Promise.reject(Error("Ouch!")); const promise2 = Promise.resolve("The second!"); Promise.race([promise1, rejection, promise2]).then(result => console.log(result) ); // The first!
若是 rejection 出如今数组的第一个元素中,则 Promise.race
被拒绝,咱们必须捕获它:
const promise1 = Promise.resolve("The first!"); const rejection = Promise.reject(Error("Ouch!")); const promise2 = Promise.resolve("The second!"); Promise.race([rejection, promise1, promise2]) .then(result => console.log(result)) .catch(error => console.error(error.message)); // Ouch!
Promise.allSettled
是对该语言的 ECMAScript 2020 补充。
这个静态方法没有什么要处理的,由于即便一个或多个输入 Promise 被拒绝,结果也始终是一个已解决的Promise 。
看下面的例子:
const promise1 = Promise.resolve("Good!"); const promise2 = Promise.reject(Error("No good, sorry!")); Promise.allSettled([promise1, promise2]) .then(results => console.log(results)) .catch(error => console.error(error)) .finally(() => console.log("Always runs!"));
咱们将由两个 Promise 组成的数组传递给 Promise.allSettled
:一个已解决,另外一个被拒绝。
在这种状况下,catch
将永远不会被执行。finally
会运行。
日志输出的 then
的代码的结果是:
[ { status: 'fulfilled', value: 'Good!' }, { status: 'rejected', reason: Error: No good, sorry! } ]
JavaScript 中的 await
表示异步函数,但从维护者的角度来看,它们受益于同步函数的全部“可读性”。
为了简单起见,咱们将使用先前的同步函数 toUppercase
,并将 async
放在 function
关键字以前,将其转换为异步函数:
async function toUppercase(string) { if (typeof string !== "string") { throw TypeError("Wrong type given, expected a string"); } return string.toUpperCase(); }
只需在函数前面加上 async
,就可使函数返回一个Promise。这意味着咱们能够在函数调用以后连接 then
,catch
和 finally
:
async function toUppercase(string) { if (typeof string !== "string") { throw TypeError("Wrong type given, expected a string"); } return string.toUpperCase(); } toUppercase("abc") .then(result => console.log(result)) .catch(error => console.error(error.message)) .finally(() => console.log("Always runs!"));
当咱们从异步函数中抛出异常时,异常会成为致使底层 Promise 被拒绝的缘由。
任何错误均可以经过外部的 catch
来拦截。
最重要的是,除了这种样式外,还可使用 try/catch/finally
,就像使用同步函数同样。
在下面的例子中,咱们从另外一个函数 consumer
调用 toUppercase
,该函数用 try/catch/finally
方便地包装函数调用:
async function toUppercase(string) { if (typeof string !== "string") { throw TypeError("Wrong type given, expected a string"); } return string.toUpperCase(); } async function consumer() { try { await toUppercase(98); } catch (error) { console.error(error.message); } finally { console.log("Always runs!"); } } consumer(); // Returning Promise ignored
输出为:
Wrong type given, expected a string Always runs!
JavaScript 中的异步生成器(Async generators) 不是生产简单值,而是可以生成 Promise 的生成器函数 。
它们将生成器函数与 async
结合在一块儿。其结果是生成器函数将 Promise 暴露给使用者的迭代器对象。
咱们用前缀为 async
和星号 *
声明一个异步生成器函数。
async function* asyncGenerator() { yield 33; yield 99; throw Error("Something went wrong!"); // Promise.reject }
基于 Promise 用于错误处理的相同规则,异步生成器中的 throw
致使 Promise 拒绝,用 catch
进行拦截。
有两种方法能够把 Promise 拉出异步生成器:
then
。从上面的例子中,在前两个 yield
以后会有一个例外。这意味着咱们能够作到:
const go = asyncGenerator(); go.next().then(value => console.log(value)); go.next().then(value => console.log(value)); go.next().catch(reason => console.error(reason.message));
这段代码的输出是:
{ value: 33, done: false } { value: 99, done: false } Something went wrong!
另外一种方法是使用异步迭代和 for await...of
。要使用异步迭代,须要用 async
函数包装使用者。
这是完整的例子:
async function* asyncGenerator() { yield 33; yield 99; throw Error("Something went wrong!"); // Promise.reject } async function consumer() { for await (const value of asyncGenerator()) { console.log(value); } } consumer();
和 async/await
同样,能够用 try/catch
处理任何潜在的异常:
async function* asyncGenerator() { yield 33; yield 99; throw Error("Something went wrong!"); // Promise.reject } async function consumer() { try { for await (const value of asyncGenerator()) { console.log(value); } } catch (error) { console.error(error.message); } } consumer();
这段代码的输出是:
33 99 Something went wrong!
从异步生成器函数返回的迭代器对象也有一个 throw()
方法,很是相似于它的同步对象。
在这里的迭代器对象上调用 throw()
不会引起异常,可是会被 Promise 拒绝:
async function* asyncGenerator() { yield 33; yield 99; yield 11; } const go = asyncGenerator(); go.next().then(value => console.log(value)); go.next().then(value => console.log(value)); go.throw(Error("Let's reject!")); go.next().then(value => console.log(value)); // value is undefined
能够经过执行如下操做从外部处理这种状况:
go.throw(Error("Let's reject!")).catch(reason => console.error(reason.message));
可是,别忘了迭代器对象 throw()
在生成器内部发送异常。这意味着咱们还能够用如下模式:
async function* asyncGenerator() { try { yield 33; yield 99; yield 11; } catch (error) { console.error(error.message); } } const go = asyncGenerator(); go.next().then(value => console.log(value)); go.next().then(value => console.log(value)); go.throw(Error("Let's reject!")); go.next().then(value => console.log(value)); // value is undefined
Node.js 中的同步错误处理与到目前为止所看到的并无太大差别。
对于同步代码,try/catch/finally
能够正常工做。
可是若是进入异步世界,事情就会变得有趣。
对于异步代码,Node.js 强烈依赖于两个习惯用法:
在回调模式中,异步 Node.js API 接受经过事件循环处理的函数,并在调用栈为空时当即执行。
看下面的代码:
const { readFile } = require("fs"); function readDataset(path) { readFile(path, { encoding: "utf8" }, function(error, data) { if (error) console.error(error); // 处理数据 }); }
若是从这个清单中提取回调,则能够看到应该如何处理错误:
// function(error, data) { if (error) console.error(error); // 处理数据 } //
若是经过使用 fs.readFile
读取给定路径而引发任何错误,将获得一个错误对象。
在这一点上,咱们能够:
要抛出异常,能够执行如下操做:
const { readFile } = require("fs"); function readDataset(path) { readFile(path, { encoding: "utf8" }, function(error, data) { if (error) throw Error(error.message); // do stuff with the data }); }
可是,与 DOM 中的事件和计时器同样,这个异常将会使程序崩溃。下面的代码尝试经过 try/catch
的处理将不起做用:
const { readFile } = require("fs"); function readDataset(path) { readFile(path, { encoding: "utf8" }, function(error, data) { if (error) throw Error(error.message); // do stuff with the data }); } try { readDataset("not-here.txt"); } catch (error) { console.error(error.message); }
若是不想使程序崩溃,则首选项是将错误传递给另外一个回调:
const { readFile } = require("fs"); function readDataset(path) { readFile(path, { encoding: "utf8" }, function(error, data) { if (error) return errorHandler(error); // do stuff with the data }); }
顾名思义,errorHandler
是一个简单的错误处理函数:
function errorHandler(error) { console.error(error.message); // do something with the error: // - write to a log. // - send to an external logger. }
咱们在 Node.js 中所作的大部分工做都是基于事件的。在大多数状况下,须要与发射器对象和一些观察者侦听消息进行交互。
Node.js 中的任何事件驱动模块(例如net)都会扩展名为 EventEmitter 的根类 。
Node.js中的 EventEmitter
有两种基本方法:on
和 emit
。
看下面这个简单的 HTTP 服务器:
const net = require("net"); const server = net.createServer().listen(8081, "127.0.0.1"); server.on("listening", function () { console.log("Server listening!"); }); server.on("connection", function (socket) { console.log("Client connected!"); socket.end("Hello client!"); });
在这里,咱们监听两个事件:listening 和 connection。
除了这些事件以外,事件发射器还暴露了 error 事件,以防发生错误。
在 80 端口上运行代码,会获得一个异常:
const net = require("net"); const server = net.createServer().listen(80, "127.0.0.1"); server.on("listening", function () { console.log("Server listening!"); }); server.on("connection", function (socket) { console.log("Client connected!"); socket.end("Hello client!"); });
输出:
events.js:291 throw er; // Unhandled 'error' event ^ Error: listen EACCES: permission denied 127.0.0.1:80 Emitted 'error' event on Server instance at: ...
要捕获它,能够为 error 注册一个事件处理函数:
server.on("error", function(error) { console.error(error.message); });
这将会输出:
listen EACCES: permission denied 127.0.0.1:80
而且程序不会崩溃。
在本文中,咱们介绍了从简单的同步代码到高级异步原语,以及整个 JavaScript 的错误处理。
在 JavaScript 程序中,能够经过多种方式来显示异常。
同步代码中的异常是最容易捕获的。而来自异步代码路径的异常处理可能会有些棘手。
同时,浏览器中的新 JavaScript API 几乎都朝着 Promise
的方向发展。这种广泛的模式使得用 then/catch/finally
或用 try/catch
来处理 async/await
异常更加容易。
看完本文后,你应该可以识别程序中可能会出现的全部不一样状况,并正确捕获异常。