07-回调地狱
14.2.4 回调地狱
回调可以帮助管理异步执行,但它却有一个实际的缺陷:当需要在执行过程中等待多个事件的时候,使用回调来管理就有点捉襟见肘了。还记得编写的第一个Node应用吗?读取三个不同文件中的内容,然后等待60秒,再把这些内容合并并写进第4个文件:
const fs = require('fs');
fs.readFile('a.txt', function(err, dataA) {
if(err) console.error(err);
fs.readFile('b.txt', function(err, dataB) {
if(err) console.error(err);
fs.readFile('c.txt', function(err, dataC) {
if(err) console.error(err);
setTimeout(function() {
fs.writeFile('d.txt', dataA+dataB+dataC, function(err) {
if(err) console.error(err);
});
}, 60*1000);
});
});
});
这就是被开发人员称为“回调地狱”的场景,这段用花括号堆起来的三角形的代码简直要逆天了,它比错误处理中出现的问题还要糟糕。在本例中,只是将错误打在了log里,但如果试着抛出异常,可能会被结果吓到。来看一个稍微简单点的例子:
const fs = require('fs');
function readSketchyFile() {
try {
fs.readFile('does_not_exist.txt', function(err, data) {
if(err) throw err;
});
} catch(err) {
console.log('warning: minor issue occurred, program continuing');
}
}
readSketchyFile();
一眼看上去,这个例子似乎很合理,同时我们也许还会为自己是一个使用异常处理的防御型开发人员而沾沾自喜。除此之处它并不能工作。如果运行这个例子你就会发现:它会导致程序崩溃。即使已经采取了一些措施来确保这些非预期的错误不会引发错误。出现这个问题的原因是 try…catch 块只在同一个函数的作用域内才有效。在本例中, try…catch 块在 readSkechyFile 函数中,但是错误却是在作为回调函数被调用的匿名函数 fs.readFile 中抛出的。
总之,没办法阻止回调函数被意外地调用两次,或者压根没有被调用。如果寄希望于它被调用且只被调用一次,那么结果只会令人失望,因为JavaScript中并没有提供防止这种意外出现的保护机制。
虽然说世上没有克服不了的困难,但随着异步代码的流行,编写低错误率且可维护的代码变得非常困难,此时promise就粉墨登场了。