错误类型500(错误类型及原因分析)
今天跟大家分享一下错误类型500(错误类型及原因分析),以下是这个问题的总结,希望对你有帮助,让我们看一看。
大家好,我是 Echa。
本文将带你了解 Javascript 中常见的错误类型,处理同步和异步 JavaScript/Node.js 代码中错误和异常的方式,以及错误处理最佳实践!
1. 错误概述
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’
堆栈跟踪是发生异常或警告等事件时程序所处的方法调用列表:
需要记住,任何传递给 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”));
此属性是一个包含所有被拒绝的错误信息的数组:
(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:806. 错误处理最佳实践
最后,我们来看看处理 JavaScript 异常的最佳实践!
(1)不要过度处理错误
错处理的第一个最佳实践就是不要过度使用“错误处理”。通常,我们会在外层处理错误,从内层抛出错误,这样一旦出现错误,就可以更好地理解是什么原因导致的。
然而,开发人员常犯的错误之一是过度使用错误处理。有时这样做是为了让代码在不同的文件和方法中看起来保持一致。但是,不幸的是,这些会对应用程序和错误检测造成不利影响。
因此,只关注代码中可能导致错误的地方,错误处理将有助于提高代码健壮性并增加检测到错误的机会。
(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
如发现本站有涉嫌抄袭侵权/违法违规等内容,请<举报!一经查实,本站将立刻删除。