延续
让我们回到在第一章中开始的异步回调的例子,但让我稍微修改它一下来画出重点:
// A
ajax( "..", function(..){
// C
} );
// B
// A
和// B
代表程序的前半部分(也就是 现在),// C
标识了程序的后半部分(也就是 稍后)。前半部分立即执行,然后会出现一个不知多久的“暂停”。在未来某个时刻,如果Ajax调用完成了,那么程序会回到它刚才离开的地方,并 继续 执行后半部分。
换句话说,回调函数包装或封装了程序的 延续。
让我们把代码弄得更简单一些:
// A
setTimeout( function(){
// C
}, 1000 );
// B
稍停片刻然后问你自己,你将如何描述(给一个不那么懂JS工作方式的人)这个程序的行为。来吧,大声说出来。这个很好的练习将使我的下一个观点更鲜明。
现在大多数读者可能在想或说着这样的话:“做A,然后设置一个等待1000毫秒的定时器,一旦它触发,就做C”。与你的版本有多接近?
你可能已经发觉了不对劲儿的地方,给了自己一个修正版:“做A,设置一个1000毫秒的定时器,然后做B,然后在超时事件触发后,做C”。这比第一个版本更准确。你能发现不同之处吗?
虽然第二个版本更准确,但是对于以一种将我们的大脑匹配代码,代码匹配JS引擎的方式讲解这段代码来说,这两个版本都是不足的。这里的鸿沟既是微小的也是巨大的,而且是理解回调作为异步表达和管理的缺点的关键。
只要我们以回调函数的方式引入一个延续(或者像许多程序员那样引入几十个!),我们就允许了一个分歧在我们的大脑如何工作和代码将运行的方式之间形成。当这两者背离时,我们的代码就不可避免地陷入这样的境地:更难理解,更难推理,更难调试,和更难维护。