错误是开发者生活的一部分。我们既不能逃避也不能躲避它们。在构建生产就绪的软件时,我们需要有效地管理错误,以。
- 改善终端用户的体验;即提供正确的信息,而不是 "无法满足请求 "的一般信息
- 开发一个强大的代码库
- 通过有效地发现错误来减少开发时间
- 避免突然停止一个程序
因为你在这里,我假设你可能是一个具有JavaScript背景的网络开发人员。让我们以Node.js中读取文件而不处理错误的典型用例为例。
var fs = require('fs')
# read a file
const data = fs.readFileSync('/Users/Kedar/node.txt')
console.log("an important piece of code that should be run at the end")
请注意,Node.js应该在读取文件的任务之后执行一些关键的代码。当我们运行它时,我们会收到如下所示的输出。
输出。
$node main.js
fs.js:641
return binding.open(pathModule._makeLong(path), stringToFlags(flags), mode);
^
Error: ENOENT: no such file or directory, open '/Users/Kedar/node.txt'
at Error (native)
at Object.fs.openSync (fs.js:641:18)
at Object.fs.readFileSync (fs.js:509:33)
at Object.<anonymous> (/home/cg/root/7717036/main.js:3:17)
at Module._compile (module.js:570:32)
at Object.Module._extensions..js (module.js:579:10)
at Module.load (module.js:487:32)
at tryModuleLoad (module.js:446:12)
at Function.Module._load (module.js:438:3)
at Module.runMain (module.js:604:10)
这里,程序没有执行必要的代码就突然结束了。我们将在后面的try...catch 块部分讨论修改后的带错误处理的代码。这个例子只是展示了没有错误处理所面临的许多问题中的一个。让我们来看看我们将涉及的内容,以便更好地理解如何处理错误。
在我们学习错误处理之前,让我们了解一下Node.js中的Error 。
错误
Error 是对 [Error](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error)对象在Javascript中的延伸。错误可以被构造并抛出 ,或者传递给一些函数。让我们来看看一些例子。
throw new Error('bad request'); // throwing new error
callback_function(new Error('connectivity issue')); // passing error as an argument
在创建错误时,我们需要传递一个人类可读的字符串作为参数,以了解当我们的程序工作不正常时出了什么问题。换句话说,我们是通过向Error 构造函数传递字符串来创建一个对象。
你还需要知道,错误和异常在JavaScript中是不同的,特别是Node.js。错误是Error 类的实例,当你抛出一个错误时,它就成为一个异常。
人类不会造成所有的错误。有两种类型的错误,程序员和操作人员。我们用 "错误 "这个短语来描述这两种错误,但由于它们的根本原因,它们在现实中是完全不同的。让我们来看看每一种。
程序员错误
程序员错误描述的是所写程序的问题--bug。换句话说,这些是由程序员在编写程序时的错误造成的错误。我们无法正确处理这些错误,纠正这些错误的唯一方法是修复代码库。下面是一些常见的程序员错误。
- 数组索引出界--在只有六个元素的情况下,试图访问数组的第七个元素
- 语法错误--在定义一个JavaScript函数时没有关闭大括号
- 引用错误--访问一个没有定义的函数或变量
- 弃用错误和警告--调用没有回调的异步函数
- 类型错误--
x对象不是可迭代的 - 未能处理操作错误
操作性错误
每个程序都会面临操作错误(即使程序是正确的)。这些都是运行期间由于外部因素造成的问题,会打断程序的正常流程。与程序员错误不同,我们可以理解并处理它们。这些是一些例子。
- 无法连接服务器/数据库
- 请求超时
- 用户的无效输入
- 套接字挂起
- 来自服务器的500响应
- 找不到文件
你可能想知道为什么这种隔离是必要的,因为两者都会导致同样的效果,即中断程序?好吧,你可能要根据错误的类型来采取行动。例如,重启应用程序可能不是一个合适的行动,因为文件未找到错误(操作错误),但当你的程序未能捕捉到被拒绝的承诺时,重启可能会有帮助(程序员错误)。
现在你知道了这些错误,让我们来处理它们。我们可以通过管理这些错误来避免程序的突然终止,这是生产就绪的代码的一个重要部分。
错误处理技术
为了有效地处理错误,我们需要了解错误传递技术。
在Node.js中,有四种基本策略来报告错误。
try…catch块- 回调
- 许诺
- 事件发射器
让我们来逐一了解使用它们。
try…catch 块
在 [try…catch](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/try...catch)方法中,try 块包围了可能发生错误的代码。换句话说,我们包裹了我们想要检查错误的代码;catch 块在这个块中处理异常。
下面是处理错误的try…catch 代码。
var fs = require('fs')
try {
const data = fs.readFileSync('/Users/Kedar/node.txt')
} catch (err) {
console.log(err)
}
console.log("an important piece of code that should be run at the end")
我们收到如下所示的输出。
$node main.js
{ Error: ENOENT: no such file or directory, open '/Users/Kedar/node.txt'
at Error (native)
at Object.fs.openSync (fs.js:641:18)
at Object.fs.readFileSync (fs.js:509:33)
at Object.<anonymous> (/home/cg/root/7717036/main.js:4:17)
at Module._compile (module.js:570:32)
at Object.Module._extensions..js (module.js:579:10)
at Module.load (module.js:487:32)
at tryModuleLoad (module.js:446:12)
at Function.Module._load (module.js:438:3)
at Module.runMain (module.js:604:10)
errno: -2,
code: 'ENOENT',
syscall: 'open',
path: '/Users/Kedar/node.txt' }
an important piece of code that should be run at the end
该错误被处理并显示。最后,代码的其他部分按计划执行。
回调
回调函数(通常用于异步代码)是我们实现错误处理的函数的一个参数。
回调函数的目的是在主函数的结果被使用之前检查错误。回调通常是主函数的最后一个参数,当出现错误或操作的结果时,它就会执行。
下面是回调函数的语法。
function (err, result) {}
第一个参数用于错误,第二个参数用于结果。如果出现错误,第一个属性将包含错误,而第二个属性将是未定义的,反之亦然。让我们看看一个例子,我们试图通过应用这种技术来读取一个文件。
const fs = require('fs');
fs.readFile('/home/Kedar/node.txt', (err, result) => {
if (err) {
console.error(err);
return;
}
console.log(result);
});
结果看起来是这样的。
$node main.js
{ Error: ENOENT: no such file or directory, open '/home/Kedar/node.txt'
at Error (native)
errno: -2,
code: 'ENOENT',
syscall: 'open',
path: '/home/Kedar/node.txt' }
我们收到了错误,因为该文件不可用。我们也可以用用户定义的函数实现回调。下面的例子说明了一个用户定义的函数,使用回调将给定的数字加倍。
const udf_double = (num, callback) => {
if (typeof callback !== 'function') {
throw new TypeError(`Expected the function. Got: ${typeof callback}`);
}
// simulate the async operation
setTimeout(() => {
if (typeof num !== 'number') {
callback(new TypeError(`Expected number, got: ${typeof num}`));
return;
}
const result = num * 2;
// callback invoked after the operation completes.
callback(null, result);
}, 100);
}
// function call
udf_double('2', (err, result) => {
if (err) {
console.error(err)
return
}
console.log(result);
});
上面的程序会出现错误,因为我们传递的是字符串而不是整数。结果如下。
$node main.js
TypeError: Expected number, got: string
at Timeout.setTimeout (/home/cg/root/7717036/main.js:9:16)
at ontimeout (timers.js:386:14)
at tryOnTimeout (timers.js:250:5)
at Timer.listOnTimeout (timers.js:214:5)
承诺
[Promise](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise)在Node.js中,承诺是一种处理错误的现代方式,与回调相比,它通常是首选。由于承诺是回调的替代品,让我们把上面讨论的例子 (udf_double) 转换为利用承诺。
const udf_double = num => {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (typeof num !== 'number') {
reject(new TypeError(`Expected number, got: ${typeof num}`));
}
const result = num * 2;
resolve(result);
}, 100);
});
}
在函数中,我们将返回一个承诺,这是我们主要逻辑的一个包装。我们在定义Promise 对象时传递了两个参数。
resolve- 用于解决承诺并提供结果reject- 用于报告/抛出错误
现在,让我们通过传递输入来执行这个函数。
udf_double('8')
.then((result) => console.log(result))
.catch((err) => console.error(err));
我们得到一个错误,如下图所示。
$node main.js
TypeError: Expected number, got: string
at Timeout.setTimeout (/home/cg/root/7717036/main.js:5:16)
at ontimeout (timers.js:386:14)
at tryOnTimeout (timers.js:250:5)
at Timer.listOnTimeout (timers.js:214:5)
好了,这看起来比回调简单多了。我们也可以使用诸如util.promisify() 这样的工具,将基于回调的代码转换为Promise 。让我们把回调部分的fs.readFile 例子转换为使用promisify 。
const fs = require('fs');
const util = require('util');
const readFile = util.promisify(fs.readFile);
readFile('/home/Kedar/node.txt')
.then((result) => console.log(result))
.catch((err) => console.error(err));
在这里,我们对readFile 函数进行承诺。我们得到的结果如下。
[Error: ENOENT: no such file or directory, open '/home/Kedar/node.txt'] {
errno: -2,
code: 'ENOENT',
syscall: 'open',
path: '/home/Kedar/node.txt'
}
Async/await
Async/await只是语法上的糖,目的是为了增强承诺。它为异步代码提供了一个同步的结构。对于简单的查询,Promise的 可以很容易地使用。
不过,如果你遇到复杂的查询场景,看起来好像是同步的代码会更容易理解。
请注意,async 函数的返回值是一个Promise 。await 等待该承诺被解决或拒绝。让我们使用async/await来实现readFile 的例子。
const fs = require('fs');
const util = require('util');
const readFile = util.promisify(fs.readFile);
const read = async () => {
try {
const result = await readFile('/home/Kedar/node.txt');
console.log(result);
} catch (err) {
console.error(err);
}
};
read()
我们正在创建async read函数,其中我们使用await 读取文件。其输出结果如下。
[Error: ENOENT: no such file or directory, open '/home/Kedar/node.txt'] {
errno: -2,
code: 'ENOENT',
syscall: 'open',
path: '/home/Kedar/node.txt'
}
事件发射器
我们可以使用事件模块中的EventEmitter类来报告复杂场景中的错误--冗长的异步操作会产生许多故障。我们可以连续发射所造成的错误,并使用发射器来监听它们。
让我们看看这个例子,我们试图模仿一个接收数据的场景,并检查它是否正确。我们需要检查前六个索引是否是整数,不包括第十二个索引。如果这六个索引中的任何一个不是整数,我们就会发出一个错误,根据这个错误做进一步的决定。
const { EventEmitter } = require('events'); //importing module
const getLetter = (index) =>{
let cypher = "*12345K%^*^&*" //will be a fetch function in a real scenario which will fetch a new cypher every time
let cipher_split = cipher.split('')
return cipher_split[index]
}
const emitterFn = () => {
const emitter = new EventEmitter(); //initializing new emitter
let counter = 0;
const interval = setInterval(() => {
counter++;
if (counter === 7) {
clearInterval(interval);
emitter.emit('end');
}
let letter = getLetter(counter)
if (isNaN(letter)) { //Check if the received value is a number
(counter<7) && emitter.emit(
'error',
new Error(`The index ${counter} needs to be a digit`)
);
return;
}
(counter<7) && emitter.emit('success', counter);
}, 1000);
return emitter;
}
const listner = emitterFn();
listner.on('end', () => {
console.info('All six indexes have been checked');
});
listner.on('success', (counter) => {
console.log(`${counter} index is an integer`);
});
listner.on('error', (err) => {
console.error(err.message);
});
首先,我们导入events 模块,使用EventEmitter 。然后,我们定义getLetter() 函数,以获取新的密码,并在每次由emitterFn() 要求的特定索引上发送值。emitterFn() 将启动EventEmitter 对象。我们逐一获取所有六个索引上的值,如果它不是一个整数,就会发出一个错误。
一个变量存储了从emitterFn() 收到的值,我们用listener.on() 来监听它们。检查完所有的索引后,程序将结束。输出看起来如下所示。
1 index is an integer
2 index is an integer
3 index is an integer
4 index is an integer
5 index is an integer
The index 6 needs to be a digit
All six indexes have been checked
处理错误
现在你知道了报告错误的技术,让我们来处理它们。
重试操作
有时,外部系统对有效的请求可能会造成错误。例如,在使用API获取一些坐标时,你得到503错误,服务不可用,这是由于过载或网络故障造成的。
这时,服务可能在几秒钟后就会恢复,报告错误可能不是最理想的做法,所以你重试操作。另外,如果你在堆栈的深处,这可能不是一个好主意,因为所有层都在不断重试,等待时间严重延长。在这种情况下,最好是中止,让客户端从他们那边重试。
向客户端报告失败
当从客户端收到错误的输入时,重试是没有意义的,因为我们可能在重新处理错误的信息时得到同样的结果。在这种情况下,最直接的方法是完成剩下的处理并向客户端报告。
直接报告堆栈顶部的故障
有时直接报告错误是合适的,因为你可能知道原因。例如,在try…catch blocks 部分讨论的ENOENT 错误是在你试图打开一个不存在的文件时产生的,你可以使用上面讨论的任何方法来报告它。通过这种方式,你可以报告创建文件来解决这个错误。
立即崩溃
在出现无法恢复的错误时,崩溃程序是最好的选择。例如,一个错误是由于在没有权限的情况下访问一个文件造成的;除了崩溃系统并让系统管理员提供访问权之外,你没有什么可以做。崩溃也是处理程序员错误以恢复系统的最实用的方法。
结论
总而言之,如果你努力写好代码并提供可靠的软件,适当的错误处理是必须的。在这篇文章中,我们了解了Node.js中的错误和处理错误的重要性。我们讨论了报告错误的初步方法:try…catch 块、回调和承诺、async/await 语法以及事件发射器。我们还学习了一旦报告这些错误后的处理方法。
最后,我希望这对你有帮助,现在你可以在你的Node.js应用程序中处理错误了。
The postError handling in Node.jsappeared first onLogRocket Blog.