当前位置:朝夕网 » 数码科技 » JavaScript 错误处理大全「值得收藏」

JavaScript 错误处理大全「值得收藏」

目录编程中有什么错误?JavaScript 中有什么错误?JavaScript 中的错误类型什么是异常?当抛出异常时会发生什么?同步错误处理常规函数的错误处理生成器函数的错误处理异步错误处理计时器

目录

编程中有什么错误?

JavaScript 中有什么错误?

JavaScript 中的错误类型

什么是异常?

当抛出异常时会发生什么?

同步错误处理

常规函数的错误处理

生成器函数的错误处理

异步错误处理

计时器错误处理

事件的错误处理

How about onerror?

怎么处理 onerror?

用 Promise 处理错误

Promise, error 和 throw

错误处理 “promisified” 计时器

Promise.all 中的错误处理

Promise.any 中的错误处理

Promise.race 中的错误处理

Promise.allSettled 中的错误处理

async/await 的错误处理

异步生成器的错误处理

Node.js中的错误处理

Node.js 中的同步错误处理

Node.js 中的异步错误处理:回调模式

Node.js 中的异步错误处理:事件发射器

总结

编程中有什么错误?

在我们的程序中,事情并非一帆风顺。

特别是在某些情况下,我们可能希望在停止程序或在发生不良状况时通知用户。例如:

程序试图打开一个不存在的文件。网络连接断开。用户进行了无效的输入。

在所有的这些情况下,我们作为程序员都会产生错误,或者让编程引擎为我们创建一些错误。

在创建错误之后,我们可以向用户通知消息,或者可以完全停止执行。

JavaScript 中有什么错误?

JavaScript 中的错误是一个对象,随后被抛出,用以终止程序。

要在 JavaScript 中创建新错误,我们调用相应的构造函数。例如,要创建一个新的通用错误,可以执行以下操作:

consterr=newError("Somethingbadhappened!");

创建错误对象时,也可以省略关键字 new:

consterr=Error("Somethingbadhappened!");

创建后,错误对象将显示三个属性:

message:带有错误信息的字符串。name:错误的类型。stack:函数执行的栈跟踪。

例如,如果我们用适当的消息创建一个新的 TypeError 对象,则 message 将携带实际的错误字符串,而 name 则为 TypeError:

constwrongType=TypeError("Wrongtypegiven,expectednumber");wrongType.message;//"Wrongtypegiven,expectednumber"wrongType.name;//"TypeError"

Firefox 还实现了一堆非标准属性,例如 columnNumber,filename 和 lineNumber。

JavaScript 中的错误类型

JavaScript 中有很多类型的错误,即:

ErrorEvalErrorInternalErrorRangeErrorReferenceErrorSyntaxErrorTypeErrorURIError

请记住,所有这些错误类型都是实际构造函数,旨在返回一个新的错误对象。

在代码中主要用 Error 和 TypeError 这两种最常见的类型来创建自己的错误对象。

但是在大多数情况下,很多错误直接来自 JavaScript 引擎,例如 InternalError 或 SyntaxError。

下面的例子是当你尝试重新为 const 赋值时,将触发 TypeError:

constname="Jules";name="Caty";//TypeError:Assignmenttoconstantvariable.

当你关键字拼错时,就会触发 SyntaxError:

vax='33';//SyntaxError:Unexpectedidentifier

或者,当你在错误的地方使用保留关键字时,例如在 async 函数之外的使用 await:

functionwrong(){await99;}wrong();//SyntaxError:awaitisonlyvalidinasyncfunction

当在页面中选择不存在的 HTML 元素时,会发生 TypeError:

Uncaught TypeError: button is null

除了这些“传统的”错误对象外,AggregateError 对象也即将能够在 JavaScript 中使用。

AggregateError 可以把多个错误很方便地包装在一起,在后面将会看到。

除了这些内置错误外,在浏览器中还可以找到:

DOMExceptionDOMError 已弃用,目前不再使用。

DOMException 是与 Web API 相关的一系列错误。有关完整列表,请参见 MDN。

什么是异常?

很多人认为错误和异常是一回事。实际上错误对象仅在抛出时才成为异常。

要在 JavaScript 中引发异常,我们使用 throw 关键字,后面跟错误对象:

constwrongType=TypeError("Wrongtypegiven,expectednumber");throwwrongType;

更常见的是缩写形式,在大多数代码库中,你都可以找到:

throwTypeError("Wrongtypegiven,expectednumber");

或者:

thrownewTypeError("Wrongtypegiven,expectednumber");

一般不会把异常抛出到函数或条件块之外,当然也有例外情况,例如:

functiontoUppercase(string){if(typeofstring!=="string"){throwTypeError("Wrongtypegiven,expectedastring");}returnstring.toUpperCase();}

在代码中我们检查函数的参数是否为字符串,如果不是则抛出异常。

从技术上讲,你可以在 JavaScript 中抛出任何东西,而不仅仅是错误对象:

throwSymbol();throw33;throw"Error!";thrownull;

但是,最好不要这样做,应该总是抛出正确的错误对象,而不是原始类型。

这样就可以通过代码库保持错误处理的一致性。其他团队成员总是能够在错误对象上访问 error.message 或 error.stack。

当抛出异常时会发生什么?

异常就像电梯在上升:一旦抛出一个异常,它就会在程序栈中冒泡,除非被卡在某个地方。

看下面的代码:

functiontoUppercase(string){if(typeofstring!=="string"){throwTypeError("Wrongtypegiven,expectedastring");}returnstring.toUpperCase();}toUppercase(4);

这段报告是 栈跟踪 将代码包装在生成器中来捕获这样的错误:

function*generate(){try{yield33;yield99;}catch(error){console.error(error.message);}}

生成器函数还可以向外部抛出异常。捕获这些异常的机制与捕获同步异常的机制相同:try/catch/finally。

下面是通过 for … of 从外部使用的生成器函数的例子:

function*generate(){yield33;yield99;throwError("Tiredofiterating!");}try{for(constvalueofgenerate()){console.log(value);}}catch(error){console.error(error.message);}/*Output:3399Tiredofiterating!*/

代码中迭代 try 块内的主处理流程。如果发生任何异常,就用 catch 停止。

异步错误处理

JavaScript 在本质上是同步的,是一种单线程语言。

诸如浏览器引擎之类的环境用许多 Web API 增强了 JavaScript,用来与外部系统进行交互并处理与 I/O 绑定的操作。

浏览器中的异步示例包括timeouts、events、Promise。

异步代码中的错误处理与同步代码不同。

看一些例子:

计时器错误处理

在你开始学习 JavaScript 时,当学 try/catch/finally 之后,你可能会想把它们放在任何代码块中。

思考下面的代码段:

functionfailAfterOneSecond(){setTimeout(()=>{throwError("Somethingwentwrong!");},1000);}

这个函数将在大约 1 秒钟后被触发。那么处理这个异常的正确方式是什么?

下面的例子是无效的:

functionfailAfterOneSecond(){setTimeout(()=>{throwError("Somethingwentwrong!");},1000);}try{failAfterOneSecond();}catch(error){console.error(error.message);}

正如前面所说的,try/catch 是同步的。另一方面,我们有 setTimeout,这是一个用于定时器的浏览器 API。

到传递给 setTimeout 的回调运行时,try/catch 已经“消失了”。程序将会崩溃,因为我们无法捕获异常。

它们在两条不同的轨道上行驶:

Track A: –> try/catchTrack B: –> setTimeout –> callback –> throw

DOM 事件的错误处理机制遵循与异步 Web API 的相同方案。

看下面的例子:

constbutton=document.querySelector("button");button.addEventListener("click",function(){throwError("Can'ttouchthisbutton!");});

在这里,单击按钮后会立即引发异常,应该怎样捕获它?下面的方法不起作用,而且不会阻止程序崩溃:

constbutton=document.querySelector("button");try{button.addEventListener("click",function(){throwError("Can'ttouchthisbutton!");});}catch(error){console.error(error.message);}

与前面的带有 setTimeout 的例子一样,传递给 addEventListener 的任何回调均异步执行:

Track A: –> try/catchTrack B: –> addEventListener –> callback –> throw functiontoUppercase(string){if(typeofstring!=="string"){returnPromise.reject(TypeError("Wrongtypegiven,expectedastring"));}constresult=string.toUpperCase();returnPromise.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("Runbaby,run"));

切记,传递给 then/catch/finally 的任何回调都是由微任务队列异步处理的。微任务优先于宏任务,例如事件和计时器。

Promise, error 和 throw

作为拒绝 Promise 的最佳方法,提供错误对象很方便:

Promise.reject(TypeError("Wrongtypegiven,expectedastring"));

这样,你可以通过代码库保持错误处理的一致性。其他团队成员总是可以期望访问 error.message,更重要的是你可以检查栈跟踪。

除了 Promise.reject 之外,可以通过抛出异常来退出 Promise 链。

看下面的例子:

Promise.resolve("Astring").then(value=>{if(typeofvalue==="string"){throwTypeError("Expectedanumber!");}});

我们用一个字符串解决一个 Promise,然后立即用 throw 打破这个链。

为了阻止异常的传播,照常使用 catch:

Promise.resolve("Astring").then(value=>{if(typeofvalue==="string"){throwTypeError("Expectedanumber!");}}).catch(reason=>console.log(reason.message));

这种模式在 fetch 中很常见,我们在其中检查相应对象并查找错误:

fetch("https://example-dev/api/").then(response=>{if(!response.ok){throwError(response.statusText);}returnresponse.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.

更好地捕获他们!

错误处理 “promisified” 计时器

使用计时器或事件无法捕获从回调引发的异常。我们在上一节中看到了例子:

functionfailAfterOneSecond(){setTimeout(()=>{throwError("Somethingwentwrong!");},1000);}//DOESNOTWORKtry{failAfterOneSecond();}catch(error){console.error(error.message);}

Promise 提供的解决方案在于代码的“promisification”。基本上,我们用 Promise 包装计时器:

functionfailAfterOneSecond(){returnnewPromise((_,reject)=>{setTimeout(()=>{reject(Error("Somethingwentwrong!"));},1000);});}

通过 reject,我们启动了Promise rejection,它带有一个错误对象。

这时可以用 catch 处理异常:

failAfterOneSecond().catch(reason=>console.error(reason.message));

注意:通常使用 value 作为 Promise 的返回值,并用 reason 作为 rejection 的返回对象。

Node.js 有一个名为promisify的工具函数,可以简化旧式回调 API 的“混杂”。

Promise.all 中的错误处理

静态方法 Promise.all 接受一个 Promise 数组,并返回所有解析 Promise 的结果数组:

constpromise1=Promise.resolve("Allgood!");constpromise2=Promise.resolve("Allgoodheretoo!");Promise.all([promise1,promise2]).then((results)=>console.log(results));//['Allgood!','Allgoodheretoo!'] Promise.all([promise1,promise2,promise3]).then(results=>console.log(results)).catch(error=>console.error(error.message)).finally(()=>console.log("Alwaysruns!"));Promise.any 中的错误处理

我们可以将 Promise.any(Firefox> 79,Chrome> 85)视为与 Promise.all 相反。

即使数组中的一个 Promise 拒绝,Promise.all 也会返回失败,而 Promise.any 总是提供第一个已解决的Promise(如果存在于数组中),无论发生了什么拒绝。

asyncfunctiontoUppercase(string){if(typeofstring!=="string"){throwTypeError("Wrongtypegiven,expectedastring");}returnstring.toUpperCase();}toUppercase("abc").then(result=>console.log(result)).catch(error=>console.error(error.message)).finally(()=>console.log("Alwaysruns!"));

当我们从异步函数中抛出异常时,异常会成为导致底层 Promise 被拒绝的原因。

任何错误都可以通过外部的 catch 来拦截。

最重要的是,除了这种样式外,还可以使用 try/catch/finally,就像使用同步函数一样。

在下面的例子中,我们从另一个函数 consumer 调用 toUppercase,该函数用 try/catch/finally 方便地包装函数调用:

asyncfunctiontoUppercase(string){if(typeofstring!=="string"){throwTypeError("Wrongtypegiven,expectedastring");}returnstring.toUpperCase();}asyncfunctionconsumer(){try{awaittoUppercase(98);}catch(error){console.error(error.message);}finally{console.log("Alwaysruns!");}}consumer();//ReturningPromiseignored

输出为:

Wrong type given, expected a stringAlways runs!异步生成器的错误处理

JavaScript 中的异步生成器(Async generators) 不是生产简单值,而是能够生成 Promise 的生成器函数 。

它们将生成器函数与 async 结合在一起。其结果是生成器函数将 Promise 暴露给使用者的迭代器对象。

我们用前缀为 async 和星号 * 声明一个异步生成器函数。

asyncfunction*asyncGenerator(){yield33;yield99;throwError("Somethingwentwrong!");//Promise.reject}

基于 Promise 用于错误处理的相同规则,异步生成器中的 throw 导致 Promise 拒绝,用 catch进行拦截。

有两种方法可以把 Promise 拉出异步生成器:

then。异步迭代。

从上面的例子中,在前两个 yield 之后会有一个例外。这意味着我们可以做到:

constgo=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 函数包装使用者。

这是完整的例子:

asyncfunction*asyncGenerator(){yield33;yield99;throwError("Somethingwentwrong!");//Promise.reject}asyncfunctionconsumer(){forawait(constvalueofasyncGenerator()){console.log(value);}}consumer();

和 async/await 一样,可以用 try/catch 处理任何潜在的异常:

asyncfunction*asyncGenerator(){yield33;yield99;throwError("Somethingwentwrong!");//Promise.reject}asyncfunctionconsumer(){try{forawait(constvalueofasyncGenerator()){console.log(value);}}catch(error){console.error(error.message);}}consumer();

这段代码的输出是:

3399Something went wrong!

从异步生成器函数返回的迭代器对象也有一个 throw() 方法,非常类似于它的同步对象。

在这里的迭代器对象上调用 throw() 不会引发异常,但是会被 Promise 拒绝:

asyncfunction*asyncGenerator(){yield33;yield99;yield11;}constgo=asyncGenerator();go.next().then(value=>console.log(value));go.next().then(value=>console.log(value));go.throw(Error("Let'sreject!"));go.next().then(value=>console.log(value));//valueisundefined

可以通过执行以下操作从外部处理这种情况:

go.throw(Error("Let'sreject!")).catch(reason=>console.error(reason.message));

但是,别忘了迭代器对象 throw() 在生成器内部发送异常。这意味着我们还可以用以下模式:

asyncfunction*asyncGenerator(){try{yield33;yield99;yield11;}catch(error){console.error(error.message);}}constgo=asyncGenerator();go.next().then(value=>console.log(value));go.next().then(value=>console.log(value));go.throw(Error("Let'sreject!"));go.next().then(value=>console.log(value));//valueisundefinedNode.js中的错误处理Node.js 中的同步错误处理

Node.js 中的同步错误处理与到目前为止所看到的并没有太大差异。

对于同步代码,try/catch/finally 可以正常工作。

但是如果进入异步世界,事情就会变得有趣。

Node.js 中的异步错误处理:回调模式

对于异步代码,Node.js 强烈依赖于两个习惯用法:

回调模式。事件发射器(event emitter)。

在回调模式中,异步 Node.js API 接受通过事件循环处理的函数,并在调用栈为空时立即执行。

看下面的代码:

const{readFile}=require("fs");functionreadDataset(path){readFile(path,{encoding:"utf8"},function(error,data){if(error)console.error(error);//处理数据});} //function(error,data){if(error)console.error(error);//处理数据}// 简单地把错误对象输出到日志。引发异常。将错误传递给另一个回调。

要抛出异常,可以执行以下操作:

const{readFile}=require("fs");functionreadDataset(path){readFile(path,{encoding:"utf8"},function(error,data){if(error)throwError(error.message);//dostuffwiththedata});}

但是,与 DOM 中的事件和计时器一样,这个异常将会使程序崩溃。下面的代码尝试通过 try/catch 的处理将不起作用:

const{readFile}=require("fs");functionreadDataset(path){readFile(path,{encoding:"utf8"},function(error,data){if(error)throwError(error.message);//dostuffwiththedata});}try{readDataset("not-here.txt");}catch(error){console.error(error.message);} 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 异常更加容易。

看完本文后,你应该能够识别程序中可能会出现的所有不同情况,并正确捕获异常。

以上就是朝夕生活(www.30zx.com)关于“JavaScript 错误处理大全「值得收藏」”的详细内容,希望对大家有所帮助!

免责声明:本文版权归原作者所有,转载文章仅为传播更多信息之目的,如有侵权行为,请第一时间联系我们修改或删除,多谢。朝夕网 » JavaScript 错误处理大全「值得收藏」