mirror of
https://github.com/apachecn/eloquent-js-3e-zh.git
synced 2025-05-23 20:02:20 +00:00
11.
This commit is contained in:
parent
f287ae6d23
commit
a5f565c196
143
11.md
143
11.md
@ -150,15 +150,17 @@ storage(bigOak, "enemies")
|
|||||||
|
|
||||||
这个异步函数返回一个有意义的值。 这是`Promise`的主要优点 - 它们简化了异步函数的使用。 基于`Promise`的函数不需要传递回调,而是类似于常规函数:它们将输入作为参数并返回它们的输出。 唯一的区别是输出可能还不可用。
|
这个异步函数返回一个有意义的值。 这是`Promise`的主要优点 - 它们简化了异步函数的使用。 基于`Promise`的函数不需要传递回调,而是类似于常规函数:它们将输入作为参数并返回它们的输出。 唯一的区别是输出可能还不可用。
|
||||||
|
|
||||||
## 失败
|
## 故障
|
||||||
|
|
||||||
|
> 译者注:这段如果有配套代码会更容易理解,但是没有,所以凑合看吧。
|
||||||
|
|
||||||
常规的 JavaScript 计算可能会因抛出异常而失败。 异步计算经常需要类似的东西。 网络请求可能会失败,或者作为异步计算的一部分的某些代码,可能会引发异常。
|
常规的 JavaScript 计算可能会因抛出异常而失败。 异步计算经常需要类似的东西。 网络请求可能会失败,或者作为异步计算的一部分的某些代码,可能会引发异常。
|
||||||
|
|
||||||
异步编程的回调风格中最紧迫的问题之一是,确保将失败正确地报告给回调函数,是非常困难的。
|
异步编程的回调风格中最紧迫的问题之一是,确保将故障正确地报告给回调函数,是非常困难的。
|
||||||
|
|
||||||
一个广泛使用的约定是,回调函数的第一个参数用于指示操作失败,第二个参数包含操作成功时生成的值。 这种回调函数必须始终检查它们是否收到异常,并确保它们引起的任何问题,包括它们调用的函数所抛出的异常,都会被捕获并提供给正确的函数。
|
一个广泛使用的约定是,回调函数的第一个参数用于指示操作失败,第二个参数包含操作成功时生成的值。 这种回调函数必须始终检查它们是否收到异常,并确保它们引起的任何问题,包括它们调用的函数所抛出的异常,都会被捕获并提供给正确的函数。
|
||||||
|
|
||||||
`Promise`使这更容易。可以解决它们(操作成功完成)或拒绝(失败)。只有在操作成功时,才会调用解析处理器(使用`then`注册),并且拒绝会自动传播给由`then`返回的新`Promise`。当一个处理器抛出一个异常时,这会自动使`then`调用产生的`Promise`被拒绝。因此,如果异步操作链中的任何元素失败,则整个链的结果被标记为拒绝,并且不会调用失败位置之后的任何常规处理器。
|
`Promise`使这更容易。可以解决它们(操作成功完成)或拒绝(故障)。只有在操作成功时,才会调用解析处理器(使用`then`注册),并且拒绝会自动传播给由`then`返回的新`Promise`。当一个处理器抛出一个异常时,这会自动使`then`调用产生的`Promise`被拒绝。因此,如果异步操作链中的任何元素失败,则整个链的结果被标记为拒绝,并且不会调用失败位置之后的任何常规处理器。
|
||||||
|
|
||||||
就像`Promise`的解析提供了一个值,拒绝它也提供了一个值,通常称为拒绝的原因。当处理器中的异常导致拒绝时,异常值将用作原因。同样,当处理器返回被拒绝的`Promise`时,拒绝流入下一个`Promise`。`Promise.reject`函数会创建一个新的,立即被拒绝的`Promise`。
|
就像`Promise`的解析提供了一个值,拒绝它也提供了一个值,通常称为拒绝的原因。当处理器中的异常导致拒绝时,异常值将用作原因。同样,当处理器返回被拒绝的`Promise`时,拒绝流入下一个`Promise`。`Promise.reject`函数会创建一个新的,立即被拒绝的`Promise`。
|
||||||
|
|
||||||
@ -171,3 +173,138 @@ storage(bigOak, "enemies")
|
|||||||
通过调用`then`和`catch`创建的`Promise`值的链条,可以看作异步值或失败沿着它移动的流水线。 由于这种链条通过注册处理器来创建,因此每个链条都有一个成功处理器或与其关联的拒绝处理器(或两者都有)。 不匹配结果类型(成功或失败)的处理器将被忽略。 但是那些匹配的对象被调用,并且它们的结果决定了下一次会出现什么样的值 -- 返回非`Promise`值时成功,当它抛出异常时拒绝,并且当它返回其中一个时是`Promise`的结果。
|
通过调用`then`和`catch`创建的`Promise`值的链条,可以看作异步值或失败沿着它移动的流水线。 由于这种链条通过注册处理器来创建,因此每个链条都有一个成功处理器或与其关联的拒绝处理器(或两者都有)。 不匹配结果类型(成功或失败)的处理器将被忽略。 但是那些匹配的对象被调用,并且它们的结果决定了下一次会出现什么样的值 -- 返回非`Promise`值时成功,当它抛出异常时拒绝,并且当它返回其中一个时是`Promise`的结果。
|
||||||
|
|
||||||
就像环境处理未捕获的异常一样,JavaScript 环境可以检测未处理`Promise`拒绝的时候,并将其报告为错误。
|
就像环境处理未捕获的异常一样,JavaScript 环境可以检测未处理`Promise`拒绝的时候,并将其报告为错误。
|
||||||
|
|
||||||
|
## 网络是困难的
|
||||||
|
|
||||||
|
偶尔,乌鸦的镜像系统没有足够的光线来传输信号,或者有些东西阻挡了信号的路径。 信号可能发送了,但从未收到。
|
||||||
|
|
||||||
|
事实上,这只会导致提供给`send`的回调永远不会被调用,这可能会导致程序停止,而不会注意到问题。 如果在没有得到回应的特定时间段内,请求会超时并报告故障,那就很好。
|
||||||
|
|
||||||
|
通常情况下,传输故障是随机事故,例如汽车的前灯会干扰光信号,只需重试请求就可以使其成功。 所以,当我们处理它时,让我们的请求函数在放弃之前自动重试发送请求几次。
|
||||||
|
|
||||||
|
而且,既然我们已经确定`Promise`是一件好事,我们也会让我们的请求函数返回一个`Promise`。 对于他们可以表达的内容,回调和`Promise`是等同的。 基于回调的函数可以打包,来公开基于`Promise`的接口,反之亦然。
|
||||||
|
|
||||||
|
即使请求及其响应已成功传递,响应也可能表明失败 - 例如,如果请求尝试使用未定义的请求类型或处理器,会引发错误。 为了支持这个,`send`和`defineRequestType`遵循前面提到的惯例,其中传递给回调的第一个参数是故障原因,如果有的话,第二个参数是实际结果。
|
||||||
|
|
||||||
|
这些可以由我们的包装翻译成`Promise`的解析和拒绝。
|
||||||
|
|
||||||
|
```js
|
||||||
|
class Timeout extends Error {}
|
||||||
|
|
||||||
|
function request(nest, target, type, content) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
let done = false;
|
||||||
|
function attempt(n) {
|
||||||
|
nest.send(target, type, content, (failed, value) => {
|
||||||
|
done = true;
|
||||||
|
if (failed) reject(failed);
|
||||||
|
else resolve(value);
|
||||||
|
});
|
||||||
|
setTimeout(() => {
|
||||||
|
if (done) return;
|
||||||
|
else if (n < 3) attempt(n + 1);
|
||||||
|
else reject(new Timeout("Timed out"));
|
||||||
|
}, 250);
|
||||||
|
}
|
||||||
|
attempt(1);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
因为承诺只能解析(或拒绝)一次,所以这个是有效的。 第一次调用`resolve`或`reject`会决定`Promise`的结果,并且任何进一步的调用(例如请求结束后到达的超时,或在另一个请求结束后返回的请求)都将被忽略。
|
||||||
|
|
||||||
|
为了构建异步循环,对于重试,我们需要使用递归函数 - 常规循环不允许我们停止并等待异步操作。 `attempt`函数尝试发送请求一次。 它还设置了超时,如果 250 毫秒后没有响应返回,则开始下一次尝试,或者如果这是第四次尝试,则以`Timeout`实例为理由拒绝该`Promise`。
|
||||||
|
|
||||||
|
每四分之一秒重试一次,一秒钟后没有响应就放弃,这绝对是任意的。 甚至有可能,如果请求确实过来了,但处理器花费了更长时间,请求将被多次传递。 我们会编写我们的处理器,并记住这个问题 - 重复的消息应该是无害的。
|
||||||
|
|
||||||
|
总的来说,我们现在不会建立一个世界级的,强大的网络。 但没关系 - 在计算方面,乌鸦没有很高的预期。
|
||||||
|
|
||||||
|
为了完全隔离我们自己的回调,我们将继续,并为`defineRequestType`定义一个包装器,它允许处理器返回一个`Promise`或明确的值,并且连接到我们的回调。
|
||||||
|
|
||||||
|
```js
|
||||||
|
function requestType(name, handler) {
|
||||||
|
defineRequestType(name, (nest, content, source,
|
||||||
|
callback) => {
|
||||||
|
try {
|
||||||
|
Promise.resolve(handler(nest, content, source))
|
||||||
|
.then(response => callback(null, response),
|
||||||
|
failure => callback(failure));
|
||||||
|
} catch (exception) {
|
||||||
|
callback(exception);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
如果处理器返回的值还不是`Promise`,`Promise.resolve`用于将转换为`Promise`。
|
||||||
|
|
||||||
|
请注意,处理器的调用必须包装在`try`块中,以确保直接引发的任何异常都会被提供给回调函数。 这很好地说明了使用原始回调正确处理错误的难度 - 很容易忘记正确处理类似的异常,如果不这样做,故障将无法报告给正确的回调。`Promise`使其大部分是自动的,因此不易出错。
|
||||||
|
|
||||||
|
## `Promise`的集合
|
||||||
|
|
||||||
|
|
||||||
|
每台鸟巢计算机在其`neighbors`属性中,都保存了传输距离内的其他鸟巢的数组。 为了检查当前哪些可以访问,您可以编写一个函数,尝试向每个鸟巢发送一个`"ping"`请求(一个简单地请求响应的请求),并查看哪些返回了。
|
||||||
|
|
||||||
|
在处理同时运行的`Promise`集合时,`Promise.all`函数可能很有用。 它返回一个`Promise`,等待数组中的所有`Promise`解决,然后解析这些`Promise`产生的值的数组(与原始数组的顺序相同)。 如果任何`Promise`被拒绝,`Promise.all`的结果本身被拒绝。
|
||||||
|
|
||||||
|
```js
|
||||||
|
requestType("ping", () => "pong");
|
||||||
|
|
||||||
|
function availableNeighbors(nest) {
|
||||||
|
let requests = nest.neighbors.map(neighbor => {
|
||||||
|
return request(nest, neighbor, "ping")
|
||||||
|
.then(() => true, () => false);
|
||||||
|
});
|
||||||
|
return Promise.all(requests).then(result => {
|
||||||
|
return nest.neighbors.filter((_, i) => result[i]);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
当一个邻居不可用时,我们不希望整个组合`Promise`失败,因为那时我们仍然不知道任何事情。 因此,在邻居集合上映射一个函数,将它们变成请求`Promise`,并附加处理器,这些处理器使成功的请求产生`true`,拒绝的产生`false`。
|
||||||
|
|
||||||
|
在组合`Promise`的处理器中,`filter`用于从`neighbors`数组中删除对应值为`false`的元素。 这利用了一个事实,`filter`将当前元素的数组索引作为其过滤函数的第二个参数(`map`,`some`和类似的高阶数组方法也一样)。
|
||||||
|
|
||||||
|
## 网络泛洪
|
||||||
|
|
||||||
|
鸟巢仅仅可以邻居通信的事实,极大地减少了这个网络的实用性。
|
||||||
|
|
||||||
|
为了将信息广播到整个网络,一种解决方案是设置一种自动转发给邻居的请求。 然后这些邻居转发给它们的邻居,直到整个网络收到这个消息。
|
||||||
|
|
||||||
|
```js
|
||||||
|
import {everywhere} from "./crow-tech";
|
||||||
|
|
||||||
|
everywhere(nest => {
|
||||||
|
nest.state.gossip = [];
|
||||||
|
});
|
||||||
|
|
||||||
|
function sendGossip(nest, message, exceptFor = null) {
|
||||||
|
nest.state.gossip.push(message);
|
||||||
|
for (let neighbor of nest.neighbors) {
|
||||||
|
if (neighbor == exceptFor) continue;
|
||||||
|
request(nest, neighbor, "gossip", message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
requestType("gossip", (nest, message, source) => {
|
||||||
|
if (nest.state.gossip.includes(message)) return;
|
||||||
|
console.log(`${nest.name} received gossip '${
|
||||||
|
message}' from ${source}`);
|
||||||
|
sendGossip(nest, message, source);
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
为了避免永远在网络上发送相同的消息,每个鸟巢都保留一组已经看到的闲话串。 为了定义这个数组,我们使用`everywhere`函数(它在每个鸟巢上运行代码)向鸟巢的状态对象添加一个属性,这是我们将保持鸟巢局部状态的地方。
|
||||||
|
|
||||||
|
当一个鸟巢收到一个重复的闲话消息,它会忽略它。每个人都盲目重新发送这些消息时,这很可能发生。 但是当它收到一条新消息时,它会兴奋地告诉它的所有邻居,除了发送消息的那个邻居。
|
||||||
|
|
||||||
|
这将导致一条新的闲话通过网络传播,如在水中的墨水一样。 即使一些连接目前不工作,如果有一条通往指定鸟巢的替代路线,闲话将通过那里到达它。
|
||||||
|
|
||||||
|
这种网络通信方式称为泛洪 - 它用一条信息充满网络,直到所有节点都拥有它。
|
||||||
|
|
||||||
|
我们可以调用`sendGossip`看看村子里的消息流。
|
||||||
|
|
||||||
|
```js
|
||||||
|
sendGossip(bigOak, "Kids with airgun in the park");
|
||||||
|
```
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user