关于 JavaScript 错误处理的最完整指南(下半部) - Go语言中文社区

关于 JavaScript 错误处理的最完整指南(下半部)


使用 Promise 处理错误

为了演示 Promise 处理方式,我们先回到一开始的那个事例:

  1. function toUppercase(string) { 
  2.   if (typeof string !== "string") { 
  3.     throw TypeError("Wrong type given, expected a string"); 
  4.   } 
  5.  
  6.   return string.toUpperCase(); 
  7.  
  8. toUppercase(4); 

相对简单抛出异常,我们可以使用 Promise.reject 和Promise.resolve:

  1. function toUppercase(string) { 
  2.   if (typeof string !== "string") { 
  3.     return Promise.reject(TypeError("Wrong type given, expected a string")); 
  4.   } 
  5.  
  6.   const result = string.toUpperCase(); 
  7.  
  8.   return Promise.resolve(result); 

因为使用了 Promise ,所以可以使用 then 来接收返回的内容,或者用 catch 来捕获出现的错误。

  1. toUppercase(99) 
  2.   .then(result => result) 
  3.   .catch(error => console.error(error.message)); 

上面的执行结果:

  1. Wrong type given, expected a string 

除了 then 和 catch , Promise 中还有 finally 方法,这类似于try/catch 中的 finally。

  1. toUppercase(99) 
  2.   .then(result => result) 
  3.   .catch(error => console.error(error.message)) 
  4.   .finally(() => console.log("Run baby, run")); 

Promise, error, 和 throw

使用 Promise.reject 可以很方便的抛出错误:

  1. Promise.reject(TypeError("Wrong type given, expected a string")); 

除了Promise.reject,我们也可以通过抛出异常来退出 Promise。

考虑以下示例:

  1. Promise.resolve("A string").then(value => { 
  2.   if (typeof value === "string") { 
  3.     throw TypeError("Expected a number!"); 
  4.   } 
  5. }); 

要停止异常传播,我们照常使用catch:

  1. Promise.resolve("A string"
  2.   .then(value => { 
  3.     if (typeof value === "string") { 
  4.       throw TypeError("Expected a number!"); 
  5.     } 
  6.   }) 
  7.   .catch(reason => console.log(reason.message)); 

这种模式在fetch中很常见:

  1. fetch("https://example-dev/api/"
  2.   .then(response => { 
  3.     if (!response.ok) { 
  4.       throw Error(response.statusText); 
  5.     } 
  6.  
  7.     return response.json(); 
  8.   }) 
  9.   .then(json => console.log(json)); 

这里可以使用catch拦截异常。如果我们失败了,或者决定不捕获它,异常可以在堆栈中自由冒泡。

使用 Promise 来处理定时器中的异常

使用定时器或事件无法捕获从回调引发的异常。

  1. function failAfterOneSecond() { 
  2.   setTimeout(() => { 
  3.     throw Error("Something went wrong!"); 
  4.   }, 1000); 
  5.  
  6. // DOES NOT WORK 
  7. try { 
  8.   failAfterOneSecond(); 
  9. } catch (error) { 
  10.   console.error(error.message); 

解决方案就是使用 Promise:

  1. function failAfterOneSecond() { 
  2.   return new Promise((_, reject) => { 
  3.     setTimeout(() => { 
  4.       reject(Error("Something went wrong!")); 
  5.     }, 1000); 
  6.   }); 

使用reject,我们启动了一个 Promise 拒绝,它携带一个错误对象。

此时,我们可以使用catch处理异常:

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

使用 Promise.all 来处理错误

Promise.all(iterable) 方法返回一个 Promise 实例,此实例在 iterable 参数内所有的 promise 都“完成(resolved)”或参数中不包含 promise 时回调完成(resolve);

  1. const promise1 = Promise.resolve("All good!"); 
  2. const promise2 = Promise.resolve("All good here too!"); 
  3.  
  4. Promise.all([promise1, promise2]).then((results) => console.log(results)); 
  5.  
  6. // [ 'All good!''All good here too!' ] 

如果参数中 promise 有一个失败(rejected),此实例回调失败(reject),失败的原因是第一个失败 promise 的结果。

  1. const promise1 = Promise.resolve("All good!"); 
  2. const promise2 = Promise.reject(Error("No good, sorry!")); 
  3. const promise3 = Promise.reject(Error("Bad day ...")); 
  4.  
  5. Promise.all([promise1, promise2, promise3]) 
  6.   .then(results => console.log(results)) 
  7.   .catch(error => console.error(error.message)); 
  8.  
  9. // No good, sorry! 

同样,无论Promise.all的结果如何运行函数,finally 都会被执行:

  1. Promise.all([promise1, promise2, promise3]) 
  2.   .then(results => console.log(results)) 
  3.   .catch(error => console.error(error.message)) 
  4.   .finally(() => console.log("Always runs!")); 

使用 Promise.any 来处理错误

Promise.any() (Firefox > 79, Chrome > 85) 接收一个 Promise 可迭代对象,只要其中的一个 promise 成功,就返回那个已经成功的 promise 。如果可迭代对象中没有一个 promise 成功(即所有的 promises 都失败/拒绝),就返回一个失败的 promise 和AggregateError类型的实例,它是 Error 的一个子类,用于把单一的错误集合在一起。本质上,这个方法和Promise.all()是相反的。

  1. const promise1 = Promise.reject(Error("No good, sorry!")); 
  2. const promise2 = Promise.reject(Error("Bad day ...")); 
  3.  
  4. Promise.any([promise1, promise2]) 
  5.   .then(result => console.log(result)) 
  6.   .catch(error => console.error(error)) 
  7.   .finally(() => console.log("Always runs!")); 

在这里,我们使用catch处理错误,输出如下:

  1. AggregateError: No Promise in Promise.any was resolved 
  2. Always runs! 

AggregateError对象具有与基本Error相同的属性,外加errors属性:

  1. // 
  2.   .catch(error => console.error(error.errors)) 
  3. // 

此属性是由reject产生的每个单独错误的数组

  1. [Error: "No good, sorry!, Error: "Bad day ..."] 

使用 Promise.race 来处理错误

Promise.race(iterable) 方法返回一个 promise,一旦迭代器中的某个promise解决或拒绝,返回的 promise就会解决或拒绝。

  1. const promise1 = Promise.resolve("The first!"); 
  2. const promise2 = Promise.resolve("The second!"); 
  3.  
  4. Promise.race([promise1, promise2]).then(result => console.log(result)); 
  5.  
  6. // The first

这里说明,第一个 Promise 比第二个行执行完。那包含拒绝的情况又是怎么样的?

  1. const promise1 = Promise.resolve("The first!"); 
  2. const rejection = Promise.reject(Error("Ouch!")); 
  3. const promise2 = Promise.resolve("The second!"); 
  4.  
  5. Promise.race([promise1, rejection, promise2]).then(result => 
  6.   console.log(result) 
  7. ); 
  8.  
  9. // The first

如果把reject放在第一个又会怎么样?

  1. const promise1 = Promise.resolve("The first!"); 
  2. const rejection = Promise.reject(Error("Ouch!")); 
  3. const promise2 = Promise.resolve("The second!"); 
  4.  
  5. Promise.race([rejection, promise1, promise2]) 
  6.   .then(result => console.log(result)) 
  7.   .catch(error => console.error(error.message)); 
  8.  
  9. // Ouch! 

使用 Promise.allSettled 来处理错误

Promise.allSettled()方法返回一个在所有给定的promise都已经fulfilled或rejected后的promise,并带有一个对象数组,每个对象表示对应的promise结果。

考虑下面示例:

  1. const promise1 = Promise.resolve("Good!"); 
  2. const promise2 = Promise.reject(Error("No good, sorry!")); 
  3.  
  4. Promise.allSettled([promise1, promise2]) 
  5.   .then(results => console.log(results)) 
  6.   .catch(error => console.error(error)) 
  7.   .finally(() => console.log("Always runs!")); 

我们传递给Promise.allSettled一个由两个Promise组成的数组:一个已解决,另一个被拒绝。

这种情况 catch 不会被执行, finally 永远会执行。

  1.   { status: 'fulfilled', value: 'Good!' }, 
  2.   { 
  3.     status: 'rejected'
  4.     reason: Error: No good, sorry! 
  5.   } 

使用 async/await 来处理错误

为了简单起见,我们使用前面的同步函数toUppercase,并通过在function关键字前放置async来将其转换为异步函数

  1. async function toUppercase(string) { 
  2.   if (typeof string !== "string") { 
  3.     throw TypeError("Wrong type given, expected a string"); 
  4.   } 
  5.  
  6.   return string.toUpperCase(); 

只要在函数前面加上async,该函数就会返回一个Promise。这意味着我们可以在函数调用之后进行then、catch和finally 操作

  1. async function toUppercase(string) { 
  2.   if (typeof string !== "string") { 
  3.     throw TypeError("Wrong type given, expected a string"); 
  4.   } 
  5.  
  6.   return string.toUpperCase(); 
  7.  
  8. toUppercase("abc"
  9.   .then(result => console.log(result)) 
  10.   .catch(error => console.error(error.message)) 
  11.   .finally(() => console.log("Always runs!")); 

当从 async 函数抛出异常时,我们就可以使用 catch 来捕获。

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

  1. async function toUppercase(string) { 
  2.   if (typeof string !== "string") { 
  3.     throw TypeError("Wrong type given, expected a string"); 
  4.   } 
  5.  
  6.   return string.toUpperCase(); 
  7.  
  8. async function consumer() { 
  9.   try { 
  10.     await toUppercase(98); 
  11.   } catch (error) { 
  12.     console.error(error.message); 
  13.   } finally { 
  14.     console.log("Always runs!"); 
  15.   } 
  16.  
  17. consumer();  

输出:

  1. Wrong type given, expected a string 
  2. Always runs! 

使用 async generators 来处理错误

JavaScript中的async generators是能够生成 Promises 而不是简单值的生成器函数。

  1. async function* asyncGenerator() { 
  2.   yield 33; 
  3.   yield 99; 
  4.   throw Error("Something went wrong!"); // Promise.reject 

基于 Promise,此处适用于错误处理的相同规则。在异步生成器中 throw 将会触发 Promise 的reject,我们可以使用catch对其进行拦截。

为了使用异步生成器的 Promise,我们可以这样做:

  • then 方法
  • 异步遍历

从上面我们知道,在两次调用 yield之后,下一次会抛出一个异常:

  1. const go = asyncGenerator(); 
  2.  
  3. go.next().then(value => console.log(value)); 
  4. go.next().then(value => console.log(value)); 
  5. go.next().catch(reason => console.error(reason.message)); 

输出结果:

  1. { value: 33, done: false } 
  2. { value: 99, done: false } 
  3. Something went wrong! 

别一种是使用 异步遍历与for await...of:

  1. async function* asyncGenerator() { 
  2.   yield 33; 
  3.   yield 99; 
  4.   throw Error("Something went wrong!"); // Promise.reject 
  5.  
  6. async function consumer() { 
  7.   for await (const value of asyncGenerator()) { 
  8.     console.log(value); 
  9.   } 
  10.  
  11. consumer(); 

有了 async/await 我们可以使用 try/catch 来捕获异常:

  1. async function* asyncGenerator() { 
  2.   yield 33; 
  3.   yield 99; 
  4.   throw Error("Something went wrong!"); // Promise.reject 
  5.  
  6. async function consumer() { 
  7.   try { 
  8.     for await (const value of asyncGenerator()) { 
  9.       console.log(value); 
  10.     } 
  11.   } catch (error) { 
  12.     console.error(error.message); 
  13.   } 
  14.  
  15. consumer(); 

输出结果:

  1. 33 
  2. 99 
  3. Something went wrong! 

从异步生成器函数返回的迭代器对象也具有throw()方法,非常类似于其同步副本。在此处的迭代器对象上调用throw()不会引发异常,但是会被Promise拒绝

  1. async function* asyncGenerator() { 
  2.   yield 33; 
  3.   yield 99; 
  4.   yield 11; 
  5.  
  6. const go = asyncGenerator(); 
  7.  
  8. go.next().then(value => console.log(value)); 
  9. go.next().then(value => console.log(value)); 
  10.  
  11. go.throw(Error("Let's reject!")); 
  12.  
  13. go.next().then(value => console.log(value)); // value is undefined 

要从外部处理这种情况,我们可以做:

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

Node 中的错误处理

Node 中的同步错误处理

Node.js 中的同步错误处理与到目前为止所看到的并没有太大差异。对于同步,使用 try/catch/finally 就可以很好的工作了。

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

对于异步代码,Node.js 主要使用这两种方式:

  • 回调模式
  • event emitters

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

考虑以下代码:

  1. const { readFile } = require("fs"); 
  2.  
  3. function readDataset(path) { 
  4.   readFile(path, { encoding: "utf8" }, function(error, data) { 
  5.     if (error) console.error(error); 
  6.     // do stuff with the data 
  7.   }); 

我们可以看到,这里处理错误的方式是使用了回调:

  1. // 
  2. function(error, data) { 
  3.     if (error) console.error(error); 
  4.     // do stuff with the data 
  5.   } 
  6. // 

如果使用fs.readFile读取给定路径而引起任何错误,我们将获得一个错误对象。

在这一点上,我们可以:

  • 简单的把对象错误打出来
  • 抛出错误
  • 把错误传到另一个回调

我们可以抛出一个异常

  1. const { readFile } = require("fs"); 
  2.  
  3. function readDataset(path) { 
  4.   readFile(path, { encoding: "utf8" }, function(error, data) { 
  5.     if (error) throw Error(error.message); 
  6.     // do stuff with the data 
  7.   }); 

但是,与 DOM 中的事件和定时器一样,此异常将使程序崩溃。通过try/catch捕获它是不起作用的:

  1. const { readFile } = require("fs"); 
  2.  
  3. function readDataset(path) { 
  4.   readFile(path, { encoding: "utf8" }, function(error, data) { 
  5.     if (error) throw Error(error.message); 
  6.     // do stuff with the data 
  7.   }); 
  8.  
  9. try { 
  10.   readDataset("not-here.txt"); 
  11. } catch (error) { 
  12.   console.error(error.message); 

如果我们不想使程序崩溃,则将错误传递给另一个回调是首选方法:

  1. const { readFile } = require("fs"); 
  2.  
  3. function readDataset(path) { 
  4.   readFile(path, { encoding: "utf8" }, function(error, data) { 
  5.     if (error) return errorHandler(error); 
  6.     // do stuff with the data 
  7.   }); 

这里的errorHandler顾名思义,是一个用于错误处理的简单函数:

  1. function errorHandler(error) { 
  2.   console.error(error.message); 
  3.   // do something with the error: 
  4.   // - write to a log. 
  5.   // - send to an external logger. 

Node.js 中的异步错误处理:event emitters

在 Node.js 中所做的大部分工作都是基于事件的。大多数情况下,emitter object 和一些观察者进行交互以侦听消息。

Node.js中的任何事件驱动模块(例如net)都扩展了一个名为EventEmitter的根类。

Node.js中的EventEmitter有两种基本方法:on和emit。

考虑以下简单的 HTTP 服务器:

  1. const net = require("net"); 
  2.  
  3. const server = net.createServer().listen(8081, "127.0.0.1"); 
  4.  
  5. server.on("listening"function () { 
  6.   console.log("Server listening!"); 
  7. }); 
  8.  
  9. server.on("connection"function (socket) { 
  10.   console.log("Client connected!"); 
  11.   socket.end("Hello client!"); 
  12. }); 

这里我们来听两个事件:listening 和connection。除了这些事件之外,event emitters 还公开一个 error 事件,以防发生错误。

如果在端口80上运行这段代码,而不是在前面的示例上侦听,将会得到一个异常:

  1. const net = require("net"); 
  2.  
  3. const server = net.createServer().listen(80, "127.0.0.1"); 
  4.  
  5. server.on("listening"function () { 
  6.   console.log("Server listening!"); 
  7. }); 
  8.  
  9. server.on("connection"function (socket) { 
  10.   console.log("Client connected!"); 
  11.   socket.end("Hello client!"); 
  12. }); 

输出:

  1. events.js:291 
  2.       throw er; // Unhandled 'error' event 
  3.       ^ 
  4.  
  5. Error: listen EACCES: permission denied 127.0.0.1:80 
  6. Emitted 'error' event on Server instance at: ... 

要捕获它,我们可以注册一个error事件处理程序:

  1. server.on("error"function(error) { 
  2.   console.error(error.message); 
  3. }); 

输出结果:

  1. listen EACCES: permission denied 127.0.0.1:80 

总结

在这个指南中,我们介绍了JavaScript的各种错误处理,从简单的同步代码到高级的异步。在JavaScript程序中,可以通过多种方式来捕获异常。

同步代码中的异常是最容易捕获的。相反,异步中的异常需要一些技巧来处理。

浏览器中的新JavaScript API几乎都偏向 Promise。then/catch/finally或try/catch的模式对于async/await的异常处理变得更加容易。

作者:Valentino Gagliardi 译者:前端小智 来源:valentinog

原文:https://www.valentinog.com/blog/error/

本文转载自微信公众号「 大迁世界」,可以通过以下二维码关注。转载本文请联系 大迁世界公众号。

版权声明:本文来源51CTO,感谢博主原创文章,遵循 CC 4.0 by-sa 版权协议,转载请附上原文出处链接和本声明。
原文链接:http://developer.51cto.com/art/202009/626129.htm
站方申明:本站部分内容来自社区用户分享,若涉及侵权,请联系站方删除。
  • 发表于 2021-05-16 19:28:33
  • 阅读 ( 1011 )
  • 分类:

0 条评论

请先 登录 后评论

官方社群

GO教程

猜你喜欢