他对这种状况却不怎么满意。他坚持认为,你不能再信任他们了(你 们受到了伤害)。对此你也只能无奈接受,并且你需要找到某种方法来保护结账代码,保 证不再出问题。
经过修补之后,你实现了像下面这样的简单临时代码,大家似乎也很满意:
var tracked = false;
analytics.trackPurchase( purchaseData, function(){
if (!tracked) {
tracked = true;
chargeCreditCard();
displayThankyouPage();
} } );
但是,后来有一个 QA 工程师问道:“如果他们根本不调用这个回调怎么办?”哎呦!之 前你们双方都没有想到这一点。
然后,你开始沿着这个兔子洞深挖下去,考虑着他们调用你的回调时所有可能的出错情 况。这里粗略列出了你能想到的分析工具可能出错的情况:
• 调用回调过早(在追踪之前);
• 调用回调过晚(或没有调用);
• 调用回调的次数太少或太多(就像你遇到过的问题!);
• 没有把所需的环境 / 参数成功传给你的回调函数;
• 吞掉可能出现的错误或异常;
• …
这感觉就像是一个麻烦列表,实际上它就是。你可能已经开始慢慢意识到,对于被传给你 无法信任的工具的每个回调,你都将不得不创建大量的混乱逻辑。
现在你应该更加明白回调地狱是多像地狱了吧。
不只是别人的代码 有些人可能会质疑这件事情是否真像我声称的那么严重。可能你没有真正和第三方工具打
这段代码对你来说应该很熟悉,因为这里我们其实就是创 建了一个 latch 来处理对回调的多个并发调用。
过很多交道,如果并不是完全没有的话。可能你使用的是带版本的 API 或者自托管的库, 所以其行为不会在你不知道的情况下被改变。
请思考这一点:你能够真正信任理论上(在自己的代码库中)你可以控制的工具吗?
不妨这样考虑:多数人都同意,至少在某种程度上我们应该在内部函数中构建一些防御性 的输入参数检查,以便减少或阻止无法预料的问题。
过分信任输入:
function addNumbers(x,y) {
// +是可以重载的,通过类型转换,也可以是字符串连接 // 所以根据传入参数的不同,这个运算并不是严格安全的 return x + y;
}
addNumbers( 21, 21 ); // 42
addNumbers( 21, "21" ); // "2121"
针对不信任输入的防御性代码:
function addNumbers(x,y) {
// 确保输入为数字
if (typeof x != "number" || typeof y != "number") {
throw Error( "Bad parameters" );
}
// 如果到达这里,可以通过+安全的进行数字相加
return x + y; }
addNumbers( 21, 21 ); // 42
addNumbers( 21, "21" ); // Error: "Bad parameters"
依旧安全但更友好一些的:
function addNumbers(x,y) { // 确保输入为数字
x = Number( x );
y = Number( y );
// +安全进行数字相加
return x + y; }
addNumbers( 21, 21 ); // 42
addNumbers( 21, "21" ); // 42
不管你怎么做,这种类型的检查 / 规范化的过程对于函数输入是很常见的,即使是对于理 论上完全可以信任的代码。大体上说,这等价于那条地缘政治原则:“信任,但要核实。”
所以,据此是不是可以推断出,对于异步函数回调的组成,我们应该要做同样的事情,而 不只是针对外部代码,甚至是我们知道在我们自己控制下的代码?当然应该。
但是,回调并没有为我们提供任何东西来支持这一点。我们不得不自己构建全部的机制, 而且通常为每个异步回调重复这样的工作最后都成了负担。
回调最大的问题是控制反转,它会导致信任链的完全断裂。
如果你的代码中使用了回调,尤其是但也不限于使用第三方工具,而且你还没有应用某种 逻辑来解决所有这些控制反转导致的信任问题,那你的代码现在已经有了 bug,即使它们 还没有给你造成损害。隐藏的 bug 也是 bug。
确实是地狱。