一、错误传播
如果代码发生了错误,又没有被try ... catch捕获,那么,程序执行流程会跳转到哪呢?
示例:
function getLength(s) {
return s.length;
}
function printLength() {
console.log(getLength('abc')); // 3
console.log(getLength(null)); // Error!
}
printLength();
如果在一个函数内部发生了错误,它自身没有捕获,错误就会被抛到外层调用函数,如果外层函数也没有捕获,该错误会一直沿着函数调用链向上抛出,直到被JavaScript引擎捕获,代码终止执行。
一直向上抛出,直到被JavaScript引擎捕获。
所以,我们不必在每一个函数内部捕获错误,只需要在合适的地方来个统一捕获,一网打尽:
function main(s) {
console.log('BEGIN main()');
try {
foo(s);
} catch (e) {
console.log('出错了:' + e);
}
console.log('END main()');
}
function foo(s) {
console.log('BEGIN foo()');
bar(s);
console.log('END foo()');
}
function bar(s) {
console.log('BEGIN bar()');
console.log('length = ' + s.length);
console.log('END bar()');
}
main(null);
当bar()
函数传入参数null
时,代码会报错,错误会向上抛给调用方foo()
函数,foo()
函数没有try ... catch语句,所以错误继续向上抛给调用方main()
函数,main()
函数有try ... catch语句,所以错误最终在main()
函数被处理了。
至于在哪些地方捕获错误比较合适,需要视情况而定。
二、异步错误处理
编写JavaScript代码时,我们要时刻牢记,JavaScript引擎是一个事件驱动的执行引擎,代码总是以单线程执行,而回调函数的执行需要等到下一个满足条件的事件出现后,才会被执行。
例如,setTimeout()
函数可以传入回调函数,并在指定若干毫秒后执行:
function printTime() {
console.log('It is time!');
}
setTimeout(printTime, 1000);
console.log('done');
上面的代码会先打印done
,1秒后才会打印It is time!
。
如果printTime()
函数内部发生了错误,我们试图用try包裹setTimeout()
是无效的:
function printTime() {
throw new Error();
}
try {
setTimeout(printTime, 1000);
console.log('done');
} catch (e) {
console.log('error');
}
原因就在于调用setTimeout()
函数时,传入的printTime
函数并未立刻执行!紧接着,JavaScript引擎会继续执行console.log('done');
语句,而此时并没有错误发生。直到1秒钟后,执行printTime
函数时才发生错误,但此时除了在printTime
函数内部捕获错误外,外层代码并无法捕获。
所以,涉及到异步代码,无法在调用时捕获,原因就是在捕获的当时,回调函数并未执行。
类似的,当我们处理一个事件时,在绑定事件的代码处,无法捕获事件处理函数的错误。
示例:
我们用下面的代码给button绑定click事件:
但是,用户输入错误时,处理函数并未捕获到错误。
这是因为:
try...catch
块通常用于捕获和处理同步代码中的错误。然而,由于事件处理函数(如点击事件处理函数)是异步执行的,因此try...catch
块无法直接捕获这些异步代码中的错误。
$btn.click
是一个事件绑定,它是异步的。因此,即使您将其放在try
块中,catch
块也无法捕获到由throw new Error('输入有误')
抛出的错误。
可以直接处理:
三、处理异步代码中的错误
3-1、使用回调函数
在早期的异步编程中,回调函数经常被用来处理错误。
通常,回调函数的第一个参数是错误对象(如果有的话),然后是结果数据。
fs.readFile('/path/to/file', 'utf8', function(err, data) {
if (err) {
// 处理错误
console.error('读取文件时出错:', err);
return;
}
// 处理文件内容
console.log(data);
});
3-2、 使用Promises
Promises 提供了一种更优雅的方式来处理异步操作及其结果或错误。
使用 .then()
处理结果,使用 .catch()
处理错误。
const fs = require('fs').promises;
fs.readFile('/path/to/file', 'utf8')
.then(data => {
// 处理文件内容
console.log(data);
})
.catch(err => {
// 处理错误
console.error('读取文件时出错:', err);
});
3-3、使用async/await
async/await
语法是基于Promises的,它提供了一种更直观、更接近于同步代码的编写方式来处理异步操作。
const fs = require('fs').promises;
async function readFileAsync() {
try {
const data = await fs.readFile('/path/to/file', 'utf8');
// 处理文件内容
console.log(data);
} catch (err) {
// 处理错误
console.error('读取文件时出错:', err);
}
}
readFileAsync();