本文将带你理解 JavaScript 中常见的缺点类型,处理同步和异步 JavaScript/Node.js 代码中缺点和非常的办法,以及缺点处理最佳实践!
JavaScript 中的缺点是一个工具,在发生缺点时会抛出该工具以停滞程序。在 JavaScript 中,可以通过布局函数来创建一个新的通用缺点:
const err = new Error("Error");
当然,也可以省略 new 关键字:
const err = Error("Error");
Error 工具有三个属性:
message:带有缺点的字符串;name: 缺点的类型;stack:函数实行的堆栈跟踪。例如,创建一个 TypeError 工具,该将携带实际的缺点字符串,其 name 将是“TypeError”:
const wrongType = TypeError("Expected number");wrongType.message; // 'Expected number'wrongType.name; // 'TypeError'
堆栈跟踪是发生非常或警告等事宜时程序所处的方法调用列表:
它首先会打印缺点名称和,然后是被调用的方法列表。每个方法调用都解释其源代码的位置和调用它的行。可以利用此数据来浏览代码库并确定导致缺点的代码段。此方法列表以堆叠的办法排列。它显示了非常首先被抛出的位置以及它如何通过堆栈方法调用传播。为非常履行捕获不会让它通过堆栈向上传播并使程序崩溃。
对付 Error 工具,Firefox 还实现了一些非标准属性:
columnNumber:缺点所在行的列号;filename:发生缺点的文件lineNumber:发生缺点的行号2. 缺点类型JavaScript 中有一系列预定义的缺点类型。只要利用者没有明确处理运用程序中的缺点,它们就会由 JavaScript 运行时自动选择和定义。
JavaScript中的缺点类型包括:
EvalErrorInternalErrorRangeErrorReferenceErrorSyntaxErrorTypeErrorURIError这些缺点类型都是实际的布局函数,旨在返回一个新的缺点工具。最常见的便是 TypeError。大多数时候,大部分缺点将直接来自 JavaScript 引擎,例如 InternalError 或 SyntaxError。
JavaScript 供应了 instanceof 运算符可以用于区分非常类型:
try { If (typeof x !== ‘number’) { throw new TypeError(‘x 应是数字’); } else if (x <= 0) { throw new RangeError('x 应大于 0'); } else { // ... }} catch (err) { if (err instanceof TypeError) { // 处理 TypeError 缺点 } else if (err instanceof RangeError) { // 处理 RangeError 缺点 } else { // 处理其他类型缺点 }}
下面来理解 JavaScript 中最常见的缺点类型,并理解它们发生的韶光和缘故原由。
(1)SyntaxErrorSyntaxError 表示语法缺点。这些缺点是最随意马虎修复的缺点之一,由于它们表明代码语法中存在缺点。由于 JavaScript 是一种阐明而非编译的脚本措辞,因此当运用程序实行包含缺点的脚本时会抛出这些缺点。在编译措辞的情形下,此类缺点在编译期间被识别。因此,在修复这些问题之前,不会创建运用程序二进制文件。
SyntaxError 发生的一些常见缘故原由是:
短缺引号短缺右括号大括号或其他字符对齐不当(2)TypeErrorTypeError 是 JavaScript 运用程序中最常见的缺点之一,当某些值不是特定的预期类型时,就会产生此缺点。
TypeError 发生的一些常见缘故原由是:
调用不是方法的工具。试图访问 null 或未定义工具的属性将字符串视为数字,反之亦然(3)ReferenceErrorReferenceError 表示引用缺点。当代码中的变量引用有问题时,会发生 ReferenceError。可能忘却在利用变量之前为其定义一个值,或者可能试图在代码中利用一个不可访问的变量。在任何情形下,通过堆栈跟踪都可以供应充足的信息来查找和修复有问题的变量引用。
ReferenceErrors 发生的一些常见缘故原由如下:
在变量名中输入缺点。试图访问其浸染域之外的块浸染域变量。在加载之前从外部库引用全局变量。(4)RangeErrorRangeError 表示范围缺点。当变量设置的值超出其合法值范围时,将抛出 RangeError。它常日发生在将值作为参数通报给函数时,并且给定值不在函数参数的范围内。当利用记录不完全的第三方库时,有时修复起来会很棘手,由于须要知道参数的可能值范围才能通报精确的值。
RangeError 发生的一些常见场景如下:
试图通过 Array 布局函数创建造孽长度的数组。将缺点的值通报给数字方法,例如 toExponential()、toPrecision()、toFixed()等。将造孽值通报给字符串函数,例如 normalize()。(5)URIErrorURIError 表示 URI缺点。当 URI 的编码和解码涌现问题时,会抛出 URIError。JavaScript 中的 URI 操作函数包括:decodeURI、decodeURIComponent 等。如果利用了缺点的参数(无效字符),就会抛出 URIError。
(6)EvalError
EvalError 表示 Eval 缺点。当 eval() 函数调用发生缺点时,会抛出 EvalError。不过,当前的 JavaScript 引擎或 ECMAScript 规范不再抛出此缺点。但是,为了向后兼容,它仍旧是存在的。
如果利用的是旧版本的 JavaScript,可能会碰着此缺点。在任何情形下,最好调查在eval()函数调用中实行的代码是否有任何非常。
(7)InternalErrorInternalError 表示内部缺点。在 JavaScript 运行时引擎发生非常时利用。它表示代码可能存在问题也可能不存在问题。
InternalError 常日只发生在两种情形下:
当 JavaScript 运行时的补丁或更新带有引发非常的缺点时(这种情形很少发生);当代码包含对付 JavaScript 引擎而言太大的实体时(例如,数组初始值设定项太大、递归太多)。办理此缺点最得当的方法便是通过缺点确定缘故原由,并在可能的情形下重构运用逻辑,以肃清 JavaScript 引擎上事情负载的溘然激增。
把稳: 当代 JavaScript 中不会抛出 EvalError 和 InternalError。
(8)创建自定义缺点类型虽然 JavaScript 供应了足够的缺点类型类列表来涵盖大多数情形,但如果这些缺点类型不能知足哀求,还可以创建新的缺点类型。这种灵巧性的根本在于 JavaScript 许可利用 throw 命令抛出任何内容。
可以通过扩展 Error 类以创建自定义缺点类:
class ValidationError extends Error { constructor(message) { super(message); this.name = "ValidationError"; }}
可以通过以下办法利用它:
throw ValidationError("未找到该属性: name")
可以利用 instanceof 关键字识别它:
try { validateForm() // 抛出 ValidationError 的代码} catch (e) { if (e instanceof ValidationError) { } else { }}
3. 抛出错误
很多人认为缺点和非常是一回事。实际上,Error 工具只有在被抛出时才会成为非常。
在 JavaScript 中抛出非常,可以利用 throw 来抛出 Error 工具:
throw TypeError("Expected number");
或者:
throw new TypeError("Expected number");
来看一个大略的例子:
function toUppercase(string) { if (typeof string !== "string") { throw TypeError("Expected string"); } return string.toUpperCase();}
在这里,我们检讨函数参数是否为字符串。如果不是,就抛出非常。
从技能上讲,我们可以在 JavaScript 中抛出任何东西,而不仅仅是 Error 工具:
throw Symbol();throw 33;throw "Error!";throw null;
但是,最好避免这样做:要抛出精确的 Error 工具,而不是原语。
4. 抛出非常时会发生什么?非常一旦抛出,就会在程序堆栈中冒泡,除非在某个地方被捕获。
来看下面的例子:
function toUppercase(string) { if (typeof string !== "string") { throw TypeError("Expected string"); } return string.toUpperCase();}toUppercase(4);
在浏览器或 Node.js 中运行此代码,程序将停滞并抛出错误:
这里还显示了发生缺点的确切行。这个缺点便是一个堆栈跟踪,有助于跟踪代码中的问题。堆栈跟踪从下到上:
at toUppercase (<anonymous>:3:11)at <anonymous>:9:1
toUppercase 函数在第 9 行调用,在第 3 行抛出错误。除了在浏览器的掌握台中查看此堆栈跟踪之外,还可以在 Error 工具的 stack 属性上访问它。
先容完这些关于缺点的根本知识之后,下面来看看同步和异步 JavaScript 代码中的缺点和非常处理。
5. 同步缺点处理(1)常规函数的缺点处理同步代码会按照代码编写顺序实行。让我们再看看前面的例子:
function toUppercase(string) { if (typeof string !== "string") { throw TypeError("Expected string"); } return string.toUpperCase();}toUppercase(4);
在这里,引擎调用并实行 toUppercase,这统统都是同步发生的。 要捕获由此类同步函数引发的非常,可以利用 try/catch/finally:
try { toUppercase(4);} catch (error) { console.error(error.message);} finally { // ...}
常日,try 会处理正常的路径,或者可能进行的函数调用。catch 就会捕获实际的非常,它吸收 Error 工具。而不管函数的结果如何,finally 语句都会运行:无论它失落败还是成功,finally 中的代码都会运行。
(2)天生器函数的缺点处理JavaScript 中的天生器函数是一种分外类型的函数。它可以随意停息和规复,除了在其内部范围和消费者之间供应双向通信通道。为了创建一个天生器函数,须要在 function 关键字后面加上一个 :
function generate() {//}
只要进入函数,就可以利用 yield 来返回值:
function generate() { yield 33; yield 99;}
天生器函数的返回值是一个迭代器工具。要从天生器中提取值,可以利用两种方法:
在迭代器工具上调用 next()利用 for...of 进行迭代以上面的代码为例,要从天生器中获取值,可以这样做:
function generate() { yield 33; yield 99;}const go = generate();
当我们调用天生器函数时,这里的 go 便是天生的迭代器工具。接下来,就可以调用 go.next() 来连续实行:
function generate() { yield 33; yield 99;}const go = generate();const firstStep = go.next().value; // 33const secondStep = go.next().value; // 99
天生器也可以接管来自调用者的值和非常。除了 next(),从天生器返回的迭代器工具还有一个 throw() 方法。利用这种方法,就可以通过向天生器中注入非常来停滞程序:
function generate() { yield 33; yield 99;}const go = generate();const firstStep = go.next().value; // 33go.throw(Error("Tired of iterating!"));const secondStep = go.next().value; // never reached
要捕获此类缺点,可以利用 try/catch 将代码包装在天生器中:
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);}
输出结果如下:
这里,try 块中包含正常的迭代。如果发生任何非常,就会用 catch 捕获它。
6. 异步缺点处理浏览器中的异步包括定时器、事宜、Promise 等。异步天下中的缺点处理与同步天下中的处理不同。下面来看一些例子。
(1)定时器的缺点处理上面我们先容了如何利用 try/catch/finally 来处理缺点,那异步中可以利用这些来处理缺点吗?先来看一个例子:
function failAfterOneSecond() { setTimeout(() => { throw Error("Wrong!"); }, 1000);}
此函数在大约 1 秒后会抛出错误。那边那里理此非常的精确方法是什么?以下代码是无效的:
function failAfterOneSecond() { setTimeout(() => { throw Error("Wrong!"); }, 1000);}try { failAfterOneSecond();} catch (error) { console.error(error.message);}
我们知道,try/catch是同步的,以是没办法这样来处理异步中的缺点。当通报给 setTimeout的回调运行时,try/catch 早已实行完毕。程序将会崩溃,由于未能捕获非常。它们是在两条路径上实行的:
A: --> try/catchB: --> setTimeout --> callback --> throw
(2)事宜的缺点处理
我们可以监听页面中任何 HTML 元素的事宜,DOM 事宜的缺点处理机制遵照与任何异步 Web API 相同的方案。
来看下面的例子:
const button = document.querySelector("button");button.addEventListener("click", function() { throw Error("error");});
这里,在单击按钮后立即抛出了非常,我们该如何捕获这个非常呢?这样写是不起浸染的,也不会阻挡程序崩溃:
const button = document.querySelector("button");try { button.addEventListener("click", function() { throw Error("error"); });} catch (error) { console.error(error.message);}
与前面的 setTimeout 例子一样,任何通报给 addEventListener 的回调都是异步实行的:
Track A: --> try/catchTrack B: --> addEventListener --> callback --> throw
如果不想让程序崩溃,为了精确处理缺点,就必须将 try/catch 放到 addEventListener 的回调中。不过这样做并不是最佳的处理办法,与 setTimeout 一样,异步代码路径抛出的非常无法从外部捕获,并且会使程序崩溃。
下面会先容 Promises 和 async/await 是如何简化异步代码的缺点处理的。
(3)onerrorHTML 元素有许多事宜处理程序,例如 onclick、onmouseenter、onchange 等。除此之外,还有 onerror,每当 <img> 标签或 <script> 等 HTML 元素命中不存在的资源时,onerror 事宜处理程序就会触发。
来看下面的例子:
<body> <img src="nowhere-to-be-found.png"></body>
当访问的资源缺失落时,浏览器的掌握台就会报错:
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);};
或者利用 addEventListener 来监听 error 事宜,当发生缺点时进行处理:
const image = document.querySelector("img");image.addEventListener("error", function(event) { console.log(event);});
此模式对付加载备用资源以代替丢失的图像或脚本很有用。不过须要记住:onerror 与 throw 或 try/catch 是无关的。
(4)Promise 的缺点处理下面来通过最上面的 toUppercase 例子看看 Promise 是如何处理缺点的:
function toUppercase(string) { if (typeof string !== "string") { throw TypeError("Expected string"); } return string.toUpperCase();}toUppercase(4);
对上面的代码进行修正,不返回大略的字符串或非常,而是分别利用 Promise.reject 和 Promise.resolve 来处理缺点和成功:
function toUppercase(string) { if (typeof string !== "string") { return Promise.reject(TypeError("Expected string")); } const result = string.toUpperCase(); return Promise.resolve(result);}
从技能上讲,这段代码中没有任何异步的内容,但它可以很好地解释 Promise 的缺点处理机制。
现在我们就可以在 then 中利用结果,并利用 catch 来处理被谢绝的 Promise:
toUppercase(99) .then(result => result) .catch(error => console.error(error.message));
输出结果如下:
在 Promise 中,catch 是用来处理缺点的。除了 catch 还有 finally,类似于 try/catch 中的finally。不管 Promise 结果如何,finally 都会实行:
toUppercase(99) .then(result => result) .catch(error => console.error(error.message)) .finally(() => console.log("Finally"));
输出结果如下:
须要记住,任何通报给 then/catch/finally 的回调都是由微任务行列步队异步处理的。 它们是微任务,优先于事宜和计时器等宏任务。
(5)Promise, error, throw作为谢绝 Promise 时的最佳实践,可以传入 error 工具:
Promise.reject(TypeError("Expected string"));
这样,在全体代码库中保持缺点处理的同等性。 其他团队成员总是可以访问 error.message,更主要的是可以检讨堆栈跟踪。
除了 Promise.reject 之外,还可以通过抛出非常来退出 Promise 实行链。来看下面的例子:
Promise.resolve("A string").then(value => { if (typeof value === "string") { throw TypeError("Expected number!"); }});
这里利用 字符串来 resolve 一个 Promise,然后实行链立即利用 throw 断开。为了停滞非常的传播,可以利用 catch 来捕获缺点:
Promise.resolve("A string") .then(value => { if (typeof value === "string") { throw TypeError("Expected number!"); } }) .catch(reason => console.log(reason.message));
这种模式在 fetch 中很常见,可以通过检讨 response 工具来查找缺点:
fetch("https://example-dev/api/") .then(response => { if (!response.ok) { throw Error(response.statusText); } return response.json(); }) .then(json => console.log(json));
这里的非常可以利用 catch 来拦截。 如果失落败了,并且没有拦截它,非常就会在堆栈中向上冒泡。这本身并没有什么问题,但不同的环境对未捕获的谢绝有不同的反应。
例如,Node.js 会让任何未处理 Promise 谢绝的程序崩溃:
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.
以是,最好去捕获缺点。
(6)利用 Promise 处理定时器缺点对付计时器或事宜,不能捕获回调抛出的非常。上面有一个例子:
function failAfterOneSecond() { setTimeout(() => { throw Error("Error"); }, 1000);}// 不生效try { failAfterOneSecond();} catch (error) { console.error(error.message);}
我们可以利用 Promise 来包装计时器:
function failAfterOneSecond() { return new Promise((_, reject) => { setTimeout(() => { reject(Error("Error")); }, 1000); });}
这里通过 reject 捕获了一个 Promise 谢绝,它带有一个 error 工具。此时就可以用 catch 来处理非常了:
failAfterOneSecond().catch(reason => console.error(reason.message));
这里利用 value 作为 Promise 的返回值,利用 reason 作为谢绝的返回工具。
(7)Promise.all 的缺点处理Promise.all 方法接管一个 Promise 数组,并返回所有解析 Promise 的结果数组:
const promise1 = Promise.resolve("one");const promise2 = Promise.resolve("two");Promise.all([promise1, promise2]).then((results) => console.log(results));// 结果: ['one', 'two']
如果这些 Promise 中的任何一个被谢绝,Promise.all 将谢绝并返回第一个被谢绝的 Promise 的缺点。
为了在 Promise.all 中处理这些情形,可以利用 catch:
const promise1 = Promise.resolve("good");const promise2 = Promise.reject(Error("Bad"));const promise3 = Promise.reject(Error("Bad+"));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("Finally"));
(8)Promise.any 的缺点处理
Promise.any 和 Promise.all 正好相反。Promise.all 如果某一个失落败,就会抛出第一个失落败的缺点。而 Promise.any 总是返回第一个成功的 Promise,无论是否发生任何谢绝。
相反,如果通报给 Promise.any 的所有 Promise 都被谢绝,那产生的缺点便是 AggregateError。 来看下面的例子:
const promise1 = Promise.reject(Error("Error"));const promise2 = Promise.reject(Error("Error+"));Promise.any([promise1, promise2]) .then(result => console.log(result)) .catch(error => console.error(error)) .finally(() => console.log("Finally"));
输出结果如下:
这里用 catch 处理缺点。AggregateError 工具具有与基本缺点相同的属性,外加一个 errors 属性:
const promise1 = Promise.reject(Error("Error"));const promise2 = Promise.reject(Error("Error+"));Promise.any([promise1, promise2]) .then(result => console.log(result)) .catch(error => console.error(error.errors)) .finally(() => console.log("Finally"));
此属性是一个包含所有被谢绝的缺点信息的数组:
(9)Promise.race 的缺点处理
Promise.race 接管一个 Promise 数组,并返回第一个成功的 Promise 的结果:
const promise1 = Promise.resolve("one");const promise2 = Promise.resolve("two");Promise.race([promise1, promise2]).then(result => console.log(result));// 结果:one
那如果有被谢绝的 Promise,但它不是传入数组中的第一个呢:
const promise1 = Promise.resolve("one");const rejection = Promise.reject(Error("Bad"));const promise2 = Promise.resolve("two");Promise.race([promise1, rejection, promise2]).then(result => console.log(result));// 结果:one
这样结果还是 one,不会影响正常的实行。
如果被谢绝的 Promise 是数组的第一个元素,则 Promise.race 谢绝,就必须要必须捕获谢绝:
const promise1 = Promise.resolve("one");const rejection = Promise.reject(Error("Bad"));const promise2 = Promise.resolve("two");Promise.race([rejection, promise1, promise2]) .then(result => console.log(result)) .catch(error => console.error(error.message));// Bad
(10)Promise.allSettled 的缺点处理
Promise.allSettled 是 ECMAScript 2020 新增的 API。它和 Promise.all 类似,不过不会被短路,也便是说当Promise全部处理完成后,可以拿到每个 Promise 的状态, 而不管其是否处理成功。
来看下面的例子:
const promise1 = Promise.resolve("Good!");const promise2 = Promise.reject(Error("Bad!"));Promise.allSettled([promise1, promise2]) .then(results => console.log(results)) .catch(error => console.error(error)) .finally(() => console.log("Finally"));
这里向 Promise.allSettled 通报了一个包含两个 Promise 的数组:一个已办理,另一个已谢绝。
输出结果如下:
(11)async/await 的缺点处理
JavaScript 中的 async/await 表示异步函数,用同步的办法去编写异步,可读性更好。
下面来改编上面的同步函数 toUppercase,通过将 async 放在 function 关键字之前将其转换为异步函数:
async function toUppercase(string) { if (typeof string !== "string") { throw TypeError("Expected string"); } return string.toUpperCase();}
只需在 function 前加上 async 前缀,就可以让函数返回一个 Promise。这意味着我们可以在函数调用之后链式调用 then、catch 和 finally:
toUppercase("hello") .then(result => console.log(result)) .catch(error => console.error(error.message)) .finally(() => console.log("Always runs!"));
当从 async 函数中抛出非常时,非常会成为底层 Promise 被谢绝的缘故原由。任何缺点都可以从外部用 catch 拦截。
除此之外,还可以利用 try/catch/finally 来处理缺点,就像在同步函数中一样。
例如,从另一个函数 consumer 中调用 toUppercase,它方便地用 try/catch/finally 包装了函数调用:
async function toUppercase(string) { if (typeof string !== "string") { throw TypeError("Expected string"); } return string.toUpperCase();}async function consumer() { try { await toUppercase(98); } catch (error) { console.error(error.message); } finally { console.log("Finally"); }}consumer();
输出结果如下:
(12)异步天生器的缺点处理
JavaScript 中的异步天生器是能够天生 Promise 而不是大略值的天生器函数。它将天生器函数与异步相结合,结果是一个天生器函数,其迭代器工具向消费者公开一个 Promise。
要创建一个异步天生器,须要声明一个带有星号 的天生器函数,前缀为 async:
async function asyncGenerator() { yield 33; yield 99; throw Error("Bad!"); // Promise.reject}
由于异步天生器是基于 Promise,以是同样适用 Promise 的缺点处理规则,在异步天生器中,throw 会导致 Promise 谢绝,可以用 catch 拦截它。
要想从异步天生器处理 Promise,可以利用 then:
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));
输出结果如下:
也利用异步迭代 for await...of。 要利用异步迭代,须要用 async 函数包装 consumer:
async function asyncGenerator() { yield 33; yield 99; throw Error("Bad"); // 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("Bad"); // Promise.reject}async function consumer() { try { for await (const value of asyncGenerator()) { console.log(value); } } catch (error) { console.error(error.message); }}consumer();
输出结果如下:
从异步天生器函数返回的迭代器工具也有一个 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("Reject!"));go.next().then(value => console.log(value));
输出结果如下:
可以通过以下办法来捕获缺点:
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("Reject!"));go.next().then(value => console.log(value));
5. Node.js 缺点处理(1)同步缺点处理
Node.js 中的同步缺点处理与 JavaScript 是一样的,可以利用 try/catch/finally。
(2)异步缺点处理:回调模式对付异步代码,Node.js 强烈依赖两个术语:
事宜发射器回调模式在回调模式中,异步 Node.js API 接管一个函数,该函数通过事宜循环处理并在调用堆栈为空时立即实行。
来看下面的例子:
const { readFile } = require("fs");function readDataset(path) { readFile(path, { encoding: "utf8" }, function(error, data) { if (error) console.error(error); // data操作 });}
这里可以看到回调中缺点处理:
function(error, data) { if (error) console.error(error); // data操作}
如果利用 fs.readFile 读取给定路径时涌现任何缺点,我们都会得到一个 error 工具。这时我们可以:
单地记录缺点工具。抛出非常。将缺点通报给另一个回调。要想抛出非常,可以这样做:
const { readFile } = require("fs");function readDataset(path) { readFile(path, { encoding: "utf8" }, function(error, data) { if (error) throw Error(error.message); // data操作 });}
但是,与 DOM 中的事宜和计时器一样,这个非常会使程序崩溃。 利用 try/catch 停滞它的考试测验将不起浸染:
const { readFile } = require("fs");function readDataset(path) { readFile(path, { encoding: "utf8" }, function(error, data) { if (error) throw Error(error.message); // 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); // data操作 });}
这里的 errorHandler 是一个大略的缺点处理函数:
function errorHandler(error) { console.error(error.message); // 处理缺点:写入日志、发送到外部logger}
(3)异步缺点处理:事宜发射器
Node.js 中的大部分事情都是基于事宜的。大多数时候,我们会与发射器工具和一些侦听的不雅观察者进行交互。
Node.js 中的任何事宜驱动模块(例如 net)都扩展了一个名为 EventEmitter 的根类。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。除了这些事宜之外,事宜发射器还公开一个缺点事宜,在涌现缺点时触发。
如果这段代码监听的端口是 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; ^Error: listen EACCES: permission denied 127.0.0.1:80Emitted '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
6. 缺点处理最佳实践
末了,我们来看看处理 JavaScript 非常的最佳实践!
错处理的第一个最佳实践便是不要过度利用“缺点处理”。常日,我们会在外层处理缺点,从内层抛出错误,这样一旦涌现缺点,就可以更好地理解是什么缘故原由导致的。
然而,开拓职员常犯的缺点之一是过度利用缺点处理。有时这样做是为了让代码在不同的文件和方法中看起来保持同等。但是,不幸的是,这些会对运用程序和缺点检测造成不利影响。
因此,只关注代码中可能导致缺点的地方,缺点处理将有助于提高代码健壮性并增加检测到缺点的机会。
(2)避免浏览器特定的非标准方法只管许多浏览器都遵照一个通用标准,但某些特定于浏览器的 JavaScript 实现在其他浏览器上却失落败了。例如,以下语法仅适用于 Firefox:
catch(e) { console.error(e.filename + ': ' + e.lineNumber); }
因此,在处理缺点时,尽可能利用跨浏览器友好的 JavaScript 代码。
(3)远程缺点记录当发生缺点时,我们该当得到关照以理解出了什么问题。这便是缺点日志的用武之地。JavaScript 代码是在用户的浏览器中实行的。因此,须要一种机制来跟踪客户端浏览器中的这些缺点,并将它们发送到做事器进行剖析。
可以考试测验利用以下工具来监控并上报缺点:
Sentry(https://sentry.io/): 专注于非常(运用崩溃)而不是信息缺点。它供应了运用中缺点的完全概述,包括受影响的用户数量、调用堆栈、受影响的浏览器以及导致缺点的提交等详细信息。Rollbar(https://rollbar.com/): 用于前端、后端和移动运用的无代理缺点监控工具。它供应人工智能赞助的事情流程,使开拓职员能够在缺点影响用户之前立即采纳行动。它会显示受缺点影响的客户数量、受影响的平台或浏览器的类型以及之前是否发生过类似缺点或是否已经存在办理方案等数据。(4)缺点处理中间件(Node.js)Node.js 环境支持利用中间件向做事端运用中添加功能。因此可以创建一个缺点处理中间件。利用中间件的最大好处是所有缺点都在一个地方集中处理。可以选择启用/禁用此设置以轻松进行测试。
以下是创建基本中间件的方法:
const logError = err => { console.log("ERROR: " + String(err))}const errorLoggerMiddleware = (err, req, res, next) => { logError(err) next(err)}const returnErrorMiddleware = (err, req, res, next) => { res.status(err.statusCode || 500) .send(err.message)}module.exports = { logError, errorLoggerMiddleware, returnErrorMiddleware}
可以像下面这样在运用中利用此中间件:
const { errorLoggerMiddleware, returnErrorMiddleware } = require('./errorMiddleware')app.use(errorLoggerMiddleware)app.use(returnErrorMiddleware)
现在可以在中间件内定义自定义逻辑以适当地处理缺点。而无需再担心在全体代码库中实现单独的缺点处理构造。
(5)捕获所有未捕获的非常(Node.js)我们可能永久无法涵盖运用中可能发生的所有缺点。因此,必须履行回退策略以捕获运用中所有未捕获的非常。
可以这样做:
process.on('uncaughtException', error => { console.log("ERROR: " + String(error)) // 其他处理机制})
还可以确定发生的缺点是标准缺点还是自定义操作缺点。根据结果,可以退出进程并重新启动它以避免意外行为。
(6)捕获所有未处理的 Promise 谢绝(Node.js)与非常不同的是,promise 谢毫不会抛出错误。因此,一个被谢绝的 promise 可能只是一个警告,这让运用有可能碰着意外行为。因此,实现处理 promise 谢绝的回退机制至关主要。
可以这样做:
const promiseRejectionCallback = error => { console.log("PROMISE REJECTED: " + String(error))}process.on('unhandledRejection', callback)
参考文章https://www.valentinog.com/blog/error/https://kinsta.com/blog/errors-in-javascript/https://blog.bitsrc.io/javascript-exception-handling-patterns-best-practices-f7d6fcab735d