Skip to content

Commit af4e56c

Browse files
authored
feat: 优化 promise 章节翻译
1 parent 1da08e3 commit af4e56c

File tree

1 file changed

+22
-155
lines changed

1 file changed

+22
-155
lines changed
Lines changed: 22 additions & 155 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,16 @@
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
*!*
179179
window.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

Comments
 (0)