You Don't Know JS: Async & Performance(第2章,Callbacks)
Chapter 2: Callbacks.
Callbacks are by far the most common way that asynchrony in JS programs is expressed and managed.
Indeed, the callback is the most fundamental async pattern in the language.
对于JS来说,回调函数是异步工作的马,它很好地完成它的任务,除了。。。.
callbacks有它的缺点。许多开发者喜欢promise是最好的异步模式。
但是如果你不了解什么是抽象概念,你不可能有效使用任何抽象概念。
Continuations 继续延伸
回调函数包裹或封装了程序的continuations.
// A setTimeout( function(){ // C }, 1000 ); // B
准确的理解这段代码:执行A, 建立一个timer(1秒后激活),然后执行B, 然后1秒到后执行C.
程序的执行顺序,和开发者正常的大脑思考顺序的不同,造成了代码的不易理解。
理解回调作为异步表达式的缺陷是非常关键的。
2个大缺陷:
- 大脑思考方式和JS异步代码的执行的冲突。导致难以维护,容易忽视导致错误。
- 回调导致信任链条的割断,每个回调都必须加验证。而使用第三方API会导致不可知问题。
Sequential Brain
作者见到不少人说自己是multitasker,意思是同时干多件事情。作者认为这是很危险的,就像开车时发短信。 texting while driving
我们的大脑有并行处理事情的功能吗?答案是不能。
当我们fake multitasking,其实更像是快速的上下文转换!因为速度非常快,从外在看起来就像是同步地平行的做多个事情。
Doing Versus Planning
大脑可以做类似同步思考事情。但实际上的做事情,需要有顺序。
We think in step-by-step terms, but the tools (callbacks) available to us in code are not expressed in a step-by-step fashion once we move from synchronous to asynchronous.
And that is why it's so hard to accurately author and reason about async JS code with callbacks: because it's not how our brain planning works.
难以理解异步代码和回调是因为人类的大脑计划工作的方式。
Nested/Chained Callbacks
也叫做回调地狱或者pyramid of doom。
That's the first major deficiency to articulate about callbacks: they express asynchrony in code in ways our brains have to fight just to keep in sync with (pun intended!)
回调的第一个主要缺陷:回调在代码中表现是异步的,而在我们的大脑中不得不保持同步。
Trust Issues
大脑的序列化思考方式和JS代码的异步驱动回调之间的不匹配仅仅是关于回调问题的一部分。
还有更多的问题。
比如和第三方程序的配合。
当你把你的部分程序的执行的控制权交给第三方,叫做inversion of control。这里就有一个不能言说的合同存在于你的代码和第三方插件之间。
Tale of Five Callbacks
一个故事,网络卖电视,信用卡支付环节的功能交给第三方,并使用回调函数等待支付。结果第三方的代码出现了问题。导致客户一共支付了5次。
Not Just Others' Code
即使不使用第三方,使用自己控制开发的API.但是:
So, contemplate this: can you even really trust utilities that you do theoretically control (in your own code base)?
Think of it this way: most of us agree that at least to some extent we should build our own internal functions with some defensive checks on the input parameters, to reduce/prevent unexpected issues.
例如:
function addNumbers(x,y) { // + is overloaded with coercion to also be // string concatenation, so this operation // isn't strictly safe depending on what's // passed in. return x + y; } addNumbers( 21, 21 ); // 42 addNumbers( 21, "21" ); // "2121"
所以需要写防御性核查/转化。
//加上判断 if (typeof x != "number" || typeof y != "number") { throw Error( "Bad parameters" ); } //或者直接转化格式 x = Number( x ); y = Number( y );
信任但是要确认(核查)。
回调函数不会援助我们任何事情,我们不得的自己建造这些核查机制。
我们不得不在每个单独的异步回调中重复的做核查。
回调最麻烦的问题是“控制转化”导致所有信任链的割断!
Trying to Save Callbacks
本节讲解几个回调的改进模式。
1.split-callback design(ES6 Promise使用了这个模式)
split-callback design is what the ES6 Promise API uses。
提供success,failure2个回调函数。
2. error-first style(Node style用在Node.js)
第一个参数储存一个错误对象,通过判断这个参数是否是empty/falsy来选择使用哪个结果。
function response(err,data) { // error? if (err) { console.error( err ); } // otherwise, assume success else { console.log( data ); } } ajax( "http://some.url.1", response );
但是上面2个模式,仍然需要观察!
首先,没有真正解决信任问题。没有防止/过滤不想要的重复内容。更严重的是,你可能同时得到成功和失败信号,或者什么都没有,因此你不得不自己写条件判断。
并且,因为没有复用,你需要在每个单独的回调中写上过滤/防御代码。
问题:
What about the trust issue of never being called?
如果没有发生回调被调用的情况,你需要建立一个timeout来取消这个事件。
问题:
还有另外的信任问题是,回调函数被调用的太早!这个例子就显示了不确定性:
function result(data) { console.log( a ); } var a = 0; ajax( "..pre-cached-url..", result ); a++;
最终打印0/1, 不确定。因为异步回调可能发生的很快,导致输出0.
因此需要加上判断函数 ajax( "..pre-cached-url..", asyncify( result ) );
因为以上的这些问题,你不得不花费大量努力时间去解决!
幸运来了!ES6来了!
Review
回调是JS异步 的基础单元!但它有很多问题
第一, 我们的大脑计划事情是序列的,块的,单线程的方式。但是回调表达异步的流是非序列的,非线性的。如果你看别人的代码就很困难。
Bad to reason about code is bad code that leads to bad bugs.
我们所需要的是,把异步用更同步,序列,块方式表示出来,就像我们的大脑思考一样。
第二, 这是更重要地,回调会遭遇控制转化。这种控制转化导致了一系列信任问题。
使用援助逻辑来解决这些信任问题是可以的,但是这很麻烦也难以维护代码,并且bug是后知后觉的,难以预防,而一旦出错就是巨大的损失。(风险是未知的,代码不能得到充足的保护,直到你被这些bug咬到!)
所以,需要有一个全面的信任解决方案,可以反复在多个我们创建的回调中使用,无需重复写在回调代码中。
我们需要比回调更好的解决方案! 未来的JS要求更精明的并且能够异步模式。后续章节将深挖这些冒出来的进化!!