11
22# 使用 promise 进行错误处理
33
4- Promise 链在错误(error)处理中十分强大。当一个 promise 被 reject 时,控制权将移交至最近的 rejection 处理程序(handler) 。这在实际开发中非常方便。
4+ promise 链在错误(error)处理中十分强大。当一个 promise 被 reject 时,控制权将移交至最近的 rejection 处理程序。这在实际开发中非常方便。
55
66例如,下面代码中所 ` fetch ` 的 URL 是错的(没有这个网站),` .catch ` 对这个 error 进行了处理:
77
88``` js run
99* ! *
10- fetch (' https://no-such-server.blabla' ) // rejects
10+ fetch (' https://no-such-server.blabla' ) // reject
1111*/ ! *
1212 .then (response => response .json ())
13- .catch (err => alert (err)) // TypeError: failed to fetch(这里的文字可能有所不同)
13+ .catch (err => alert (err)) // TypeError: Failed to fetch(这里的文字可能有所不同)
1414```
1515
1616正如你所看到的,` .catch ` 不必是立即的。它可能在一个或多个 ` .then ` 之后出现。
@@ -38,11 +38,11 @@ fetch('/article/promise-chaining/user.json')
3838*/ ! *
3939```
4040
41- 通常情况下,这样的 ` .catch ` 根本不会被触发。但是如果上述任意一个 promise 被 reject (网络问题或者无效的 json 或其他),` .catch ` 就会捕获它。
41+ 通常情况下,这样的 ` .catch ` 根本不会被触发。但是如果上述任意一个 promise rejected (网络问题或者无效的 json 或其他),` .catch ` 就会捕获它。
4242
4343## 隐式 try..catch
4444
45- Promise 的执行者(executor)和 promise 的处理程序(handler)周围有一个 “隐式的 ` try..catch ` ”。如果发生异常,它(译注:指异常)就会被捕获 ,并被视为 rejection 进行处理。
45+ promise 的执行者(executor)和 promise 的处理程序周围有一个 “隐式的 ` try..catch ` ”。如果发生异常,它就会被捕获 ,并被视为 rejection 进行处理。
4646
4747例如,下面这段代码:
4848
@@ -66,7 +66,7 @@ new Promise((resolve, reject) => {
6666
6767在 executor 周围的“隐式 ` try..catch ` ”自动捕获了 error,并将其变为 rejected promise。
6868
69- 这不仅仅发生在 executor 函数中,同样也发生在其 handler 中 。如果我们在 ` .then ` 处理程序(handler)中 ` throw ` ,这意味着 promise 被 rejected,因此控制权移交至最近的 error 处理程序(handler) 。
69+ 这不仅仅发生在 executor 函数中,同样也发生在其处理程序中 。如果我们在 ` .then ` 处理程序中 ` throw ` ,这意味着 promise rejected,因此控制权移交至最近的 error 处理程序。
7070
7171这是一个例子:
7272
@@ -92,15 +92,15 @@ new Promise((resolve, reject) => {
9292}).catch (alert); // ReferenceError: blabla is not defined
9393```
9494
95- 最后的 ` .catch ` 不仅会捕获显式的 rejection,还会捕获它上面的处理程序(handler)中意外出现的 error。
95+ 最后的 ` .catch ` 不仅会捕获显式的 rejection,还会捕获它上面的处理程序中意外出现的 error。
9696
9797## 再次抛出(Rethrowing)
9898
99- 正如我们已经注意到的,链尾端的 ` .catch ` 的表现有点像 ` try..catch ` 。我们可能有许多个 ` .then ` 处理程序(handler) ,然后在尾端使用一个 ` .catch ` 处理上面的所有 error。
99+ 正如我们已经注意到的,链尾端的 ` .catch ` 的表现有点像 ` try..catch ` 。我们可能有许多个 ` .then ` 处理程序,然后在尾端使用一个 ` .catch ` 处理上面的所有 error。
100100
101- 在常规的 ` try..catch ` 中,我们可以分析错误( error) ,如果我们无法处理它,可以将其再次抛出。对于 promise 来说,这也是可以的。
101+ 在常规的 ` try..catch ` 中,我们可以分析 error,如果我们无法处理它,可以将其再次抛出。对于 promise 来说,这也是可以的。
102102
103- 如果我们在 ` .catch ` 中 ` throw ` ,那么控制权就会被移交到下一个最近的 error 处理程序(handler) 。如果我们处理该 error 并正常完成,那么它将继续到最近的成功的 ` .then ` 处理程序(handler) 。
103+ 如果我们在 ` .catch ` 中 ` throw ` ,那么控制权就会被移交到下一个最近的 error 处理程序。如果我们处理该 error 并正常完成,那么它将继续到最近的成功的 ` .then ` 处理程序。
104104
105105在下面这个例子中,` .catch ` 成功处理了 error:
106106
@@ -117,9 +117,9 @@ new Promise((resolve, reject) => {
117117}).then (() => alert (" Next successful handler runs" ));
118118```
119119
120- 这里 ` .catch ` 块正常完成。所以下一个成功的 ` .then ` 处理程序(handler)就会被调用 。
120+ 这里 ` .catch ` 块正常完成。所以下一个成功的 ` .then ` 处理程序就会被调用 。
121121
122- 在下面的例子中,我们可以看到 ` .catch ` 的另一种情况。` (*) ` 行的处理程序(handler)捕获了 error,但无法处理它(例如,它只知道如何处理 ` URIError ` ),所以它将其再次抛出:
122+ 在下面的例子中,我们可以看到 ` .catch ` 的另一种情况。` (*) ` 行的处理程序捕获了 error,但无法处理它(例如,它只知道如何处理 ` URIError ` ),所以它将其再次抛出:
123123
124124``` js run
125125// 执行流:catch -> catch
@@ -160,26 +160,26 @@ new Promise(function() {
160160 noSuchFunction (); // 这里出现 error(没有这个函数)
161161})
162162 .then (() => {
163- // 一个或多个成功的 promise 处理程序(handler)
163+ // 一个或多个成功的 promise 处理程序
164164 }); // 尾端没有 .catch!
165165```
166166
167- 如果出现 error,promise 的状态将变为 "rejected",然后执行应该跳转至最近的 rejection 处理程序(handler)。但是上面这个例子中并没有这样的处理程序(handler) 。因此 error 会“卡住(stuck) ”。没有代码来处理它。
167+ 如果出现 error,promise 的状态将变为 "rejected",然后执行应该跳转至最近的 rejection 处理程序。但上面这个例子中并没有这样的处理程序 。因此 error 会“卡住”。没有代码来处理它。
168168
169169在实际开发中,就像代码中常规的未处理的 error 一样,这意味着某些东西出了问题。
170170
171- 当发生一个常规的错误( error) 并且未被 ` try..catch ` 捕获时会发生什么?脚本死了,并在控制台(console)中留下了一个信息 。对于在 promise 中未被处理的 rejection,也会发生类似的事儿 。
171+ 当发生一个常规的 error 并且未被 ` try..catch ` 捕获时会发生什么?脚本死了,并在控制台中留下了一个信息 。对于在 promise 中未被处理的 rejection,也会发生类似的事 。
172172
173- JavaScript 引擎会跟踪此类 rejection,在这种情况下会生成一个全局的 error。如果你运行上面这个代码,你可以在控制台(console)中看到 。
173+ JavaScript 引擎会跟踪此类 rejection,在这种情况下会生成一个全局的 error。如果你运行上面这个代码,你可以在控制台中看到 。
174174
175175在浏览器中,我们可以使用 ` unhandledrejection ` 事件来捕获这类 error:
176176
177177``` js run
178178* ! *
179179window .addEventListener (' unhandledrejection' , function (event ) {
180180 // 这个事件对象有两个特殊的属性:
181- alert (event .promise ); // [object Promise] - 生成该全局 error 的 promise
182- alert (event .reason ); // Error: Whoops! - 未处理的 error 对象
181+ alert (event .promise ); // [object Promise] —— 生成该全局 error 的 promise
182+ alert (event .reason ); // Error: Whoops! —— 未处理的 error 对象
183183});
184184*/ ! *
185185
@@ -190,149 +190,16 @@ new Promise(function() {
190190
191191这个事件是 [ HTML 标准] ( https://html.spec.whatwg.org/multipage/webappapis.html#unhandled-promise-rejections ) 的一部分。
192192
193- 如果出现了一个 error,并且在这儿没有 ` .catch ` ,那么 ` unhandledrejection ` 处理程序(handler)就会被触发 ,并获取具有 error 相关信息的 ` event ` 对象,所以我们就能做一些后续处理了。
193+ 如果出现了一个 error,并且在这没有 ` .catch ` ,那么 ` unhandledrejection ` 处理程序就会被触发 ,并获取具有 error 相关信息的 ` event ` 对象,所以我们就能做一些后续处理了。
194194
195195通常此类 error 是无法恢复的,所以我们最好的解决方案是将问题告知用户,并且可以将事件报告给服务器。
196196
197197在 Node.js 等非浏览器环境中,有其他用于跟踪未处理的 error 的方法。
198198
199199## 总结
200200
201- - ` .catch ` 处理 promise 中的各种 error:在 ` reject() ` 调用中的,或者在处理程序(handler)中抛出的(thrown) error。
201+ - ` .catch ` 处理 promise 中的各种 error:在 ` reject() ` 调用中的,或者在处理程序中抛出的 error。
202202- 如果给定 ` .then ` 的第二个参数(即 error 处理程序),那么 ` .then ` 也会以相同的方式捕获 error。
203- - 我们应该将 ` .catch ` 准确地放到我们想要处理 error,并知道如何处理这些 error 的地方。处理程序应该分析 error(可以自定义 error 类来帮助分析)并再次抛出未知的 error(可能它们是编程错误 )。
204- - 如果没有办法从 error 中恢复的话 ,不使用 ` .catch ` 也可以。
203+ - 我们应该将 ` .catch ` 准确地放到我们想要处理 error,并知道如何处理这些 error 的地方。处理程序应该分析 error(可以自定义 error 类来帮助分析)并再次抛出未知的 error(它们可能是编程错误 )。
204+ - 如果没有办法从 error 中恢复 ,不使用 ` .catch ` 也可以。
205205- 在任何情况下我们都应该有 ` unhandledrejection ` 事件处理程序(用于浏览器,以及其他环境的模拟),以跟踪未处理的 error 并告知用户(可能还有我们的服务器)有关信息,以使我们的应用程序永远不会“死掉”。
206-
207- ## 补充内容
208-
209- ``` smart header="说明"
210- 为了更清晰地讲解 promise,本文经过大幅重写,以下内容是重写时被优化掉的内容,译者认为还是很有学习价值的,遂保留下来供大家学习。
211- ```
212-
213- ### Fetch 错误处理示例
214-
215- 让我们改进用户加载(user-loading)示例的错误处理。
216-
217- 当请求无法发出时,[ fetch] ( mdn:api/WindowOrWorkerGlobalScope/fetch ) reject 会返回 promise。例如,远程服务器无法访问,或者 URL 异常。但是如果远程服务器返回响应错误 404,甚至是错误 500,这些都被认为是合法的响应。
218-
219- 如果在 ` (*) ` 行,服务器返回一个错误 500 的非 JSON(non-JSON)页面该怎么办?如果没有这个用户,GitHub 返回错误 404 的页面又该怎么办呢?
220-
221- ``` js run
222- fetch (' no-such-user.json' ) // (*)
223- .then (response => response .json ())
224- .then (user => fetch (` https://api.github.com/users/${ user .name } ` )) // (**)
225- .then (response => response .json ())
226- .catch (alert); // SyntaxError: Unexpected token < in JSON at position 0
227- // ...
228- ```
229-
230- 到目前为止,代码试图以 JSON 格式加载响应数据,但无论如何都会因为语法错误而失败。你可以通过执行上述例子来查看相关信息,因为文件 ` no-such-user.json ` 不存在。
231-
232- 这有点糟糕,因为错误只是落在链上,并没有相关细节信息:什么失败了,在哪里失败的。
233-
234- 因此我们多添加一步:我们应该检查具有 HTTP 状态的 ` response.status ` 属性,如果不是 200 就抛出错误。
235-
236- ``` js run
237- class HttpError extends Error { // (1)
238- constructor (response ) {
239- super (` ${ response .status } for ${ response .url } ` );
240- this .name = ' HttpError' ;
241- this .response = response;
242- }
243- }
244-
245- function loadJson (url ) { // (2)
246- return fetch (url)
247- .then (response => {
248- if (response .status == 200 ) {
249- return response .json ();
250- } else {
251- throw new HttpError (response);
252- }
253- })
254- }
255-
256- loadJson (' no-such-user.json' ) // (3)
257- .catch (alert); // HttpError: 404 for .../no-such-user.json
258- ```
259-
260- 1 . 我们为 HTTP 错误创建一个自定义类用于区分 HTTP 错误和其他类型错误。此外,新的类有一个 constructor,它接受 ` response ` 对象,并将其保存到 error 中。因此,错误处理(error-handling)代码就能够获得响应数据了。
261- 2 . 然后我们将请求(requesting)和错误处理代码包装进一个函数,它能够 fetch ` url ` ** 并** 将所有状态码不是 200 视为错误。这很方便,因为我们通常需要这样的逻辑。
262- 3 . 现在 ` alert ` 显示更多有用的描述信息。
263-
264- 拥有我们自己的错误处理类的好处是我们可以使用 ` instanceof ` 很容易地在错误处理代码中检查错误。
265-
266- 例如,我们可以创建请求,如果我们得到 404 就可以告知用户修改信息。
267-
268- 下面的代码从 GitHub 加载给定名称的用户。如果没有这个用户,它将告知用户填写正确的名称:
269-
270- ``` js run
271- function demoGithubUser () {
272- let name = prompt (" Enter a name?" , " iliakan" );
273-
274- return loadJson (` https://api.github.com/users/${ name} ` )
275- .then (user => {
276- alert (` Full name: ${ user .name } .` );
277- return user;
278- })
279- .catch (err => {
280- * ! *
281- if (err instanceof HttpError && err .response .status == 404 ) {
282- */ ! *
283- alert (" No such user, please reenter." );
284- return demoGithubUser ();
285- } else {
286- throw err; // (*)
287- }
288- });
289- }
290-
291- demoGithubUser ();
292- ```
293-
294- 请注意:这里的 ` .catch ` 会捕获所有错误,但是它仅仅“知道如何处理” ` HttpError 404 ` 。在那种特殊情况下,它意味着没有这样的用户,而 ` .catch ` 仅仅在这种情况下重试。
295-
296- 对于其他错误,它不知道会出现什么问题。可能是编程错误或者其他错误。所以它仅仅是在 ` (*) ` 行再次抛出。
297-
298- ### 其他
299-
300- 如果我们有加载指示(load-indication),` .finally ` 是一个很好的处理程序(handler),在 fetch 完成时停止它:
301-
302- ``` js run
303- function demoGithubUser () {
304- let name = prompt (" Enter a name?" , " iliakan" );
305-
306- * ! *
307- document .body .style .opacity = 0.3 ; // (1) 开始指示(indication)
308- */ ! *
309-
310- return loadJson (` https://api.github.com/users/${ name} ` )
311- * ! *
312- .finally (() => { // (2) 停止指示(indication)
313- document .body .style .opacity = ' ' ;
314- return new Promise (resolve => setTimeout (resolve)); // (*)
315- })
316- */ ! *
317- .then (user => {
318- alert (` Full name: ${ user .name } .` );
319- return user;
320- })
321- .catch (err => {
322- if (err instanceof HttpError && err .response .status == 404 ) {
323- alert (" No such user, please reenter." );
324- return demoGithubUser ();
325- } else {
326- throw err;
327- }
328- });
329- }
330-
331- demoGithubUser ();
332- ```
333-
334- 此处的 ` (1) ` 行,我们通过调暗文档来指示加载。指示方法没有什么问题,可以使用任何类型的指示来代替。
335-
336- 当 promise 得以解决,fetch 可以是成功或者错误,` finally ` 在 ` (2) ` 行触发并终止加载指示。
337-
338- 有一个浏览器技巧,` (*) ` 是从 ` finally ` 返回零延时(zero-timeout)的 promise。这是因为一些浏览器(比如 Chrome)需要“一点时间”外的 promise 处理程序来绘制文档的更改。因此它确保在进入链下一步之前,指示在视觉上是停止的。
0 commit comments