网络是困难的
偶尔,乌鸦的镜像系统没有足够的光线来传输信号,或者有些东西阻挡了信号的路径。 信号可能发送了,但从未收到。
事实上,这只会导致提供给send的回调永远不会被调用,这可能会导致程序停止,而不会注意到问题。 如果在没有得到回应的特定时间段内,请求会超时并报告故障,那就很好。
通常情况下,传输故障是随机事故,例如汽车的前灯会干扰光信号,只需重试请求就可以使其成功。 所以,当我们处理它时,让我们的请求函数在放弃之前自动重试发送请求几次。
而且,既然我们已经确定Promise是一件好事,我们也会让我们的请求函数返回一个Promise。 对于他们可以表达的内容,回调和Promise是等同的。 基于回调的函数可以打包,来公开基于Promise的接口,反之亦然。
即使请求及其响应已成功传递,响应也可能表明失败 - 例如,如果请求尝试使用未定义的请求类型或处理器,会引发错误。 为了支持这个,send和defineRequestType遵循前面提到的惯例,其中传递给回调的第一个参数是故障原因,如果有的话,第二个参数是实际结果。
这些可以由我们的包装翻译成Promise的解析和拒绝。
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);});}
因为Promise只能解析(或拒绝)一次,所以这个是有效的。 第一次调用resolve或reject会决定Promise的结果,并且任何进一步的调用(例如请求结束后到达的超时,或在另一个请求结束后返回的请求)都将被忽略。
为了构建异步循环,对于重试,我们需要使用递归函数 - 常规循环不允许我们停止并等待异步操作。 attempt函数尝试发送请求一次。 它还设置了超时,如果 250 毫秒后没有响应返回,则开始下一次尝试,或者如果这是第四次尝试,则以Timeout实例为理由拒绝该Promise。
每四分之一秒重试一次,一秒钟后没有响应就放弃,这绝对是任意的。 甚至有可能,如果请求确实过来了,但处理器花费了更长时间,请求将被多次传递。 我们会编写我们的处理器,并记住这个问题 - 重复的消息应该是无害的。
总的来说,我们现在不会建立一个世界级的,强大的网络。 但没关系 - 在计算方面,乌鸦没有很高的预期。
为了完全隔离我们自己的回调,我们将继续,并为defineRequestType定义一个包装器,它允许处理器返回一个Promise或明确的值,并且连接到我们的回调。
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使其大部分是自动的,因此不易出错。
