JavaScript中最常见的反模式做法是,回调内部再嵌套回调。还记得前言里提到的金字塔厄运吗?我们先来看一个具体的例子,你也可能在Node服务器上看到过类似的代码。 function checkPassword(username, passwordGuess, callback) { var queryStr = 'SELECT * FROM user WHERE username = ?'; db.query(queryStr, username, function (err, result) { if (err) throw err; hash(passwordGuess, function(passwordGuessHash) { callback(passwordGuessHash === result['password_hash']); }); }); } 这里定义了一个异步函数checkPassword,它触发了另一个异步函数db.query,而后者又可能触发另外一个异步函数hash。(在阅读代码之前,无法确认这些函数是否真的异步,但这里的几个函数理应如此。) 这段代码有什么问题呢?目前为止,没有任何问题。它能用,而且简洁明了。但是,如果试图向其添加新特性,它就会变得毛里毛躁、险象环生,比如去处理那个数据库错误,而不是抛出错误(请参阅1.4.3节)、记录尝试访问数据库的次数、阻塞访问数据库,等等。 嵌套式回调诱惑我们通过添加更多代码来添加更多特性,而不是将这些特性实现为可管理、可重用的代码片段。checkPassword有一种可以避免出现上述苗头的等价实现方式,如下: function checkPassword(username, passwordGuess, callback) { var passwordHash; var queryStr = 'SELECT * FROM user WHERE username = ?'; db.query(qyeryStr, username, queryCallback); function queryCallback(err, result) { if (err) throw err; passwordHash = result['password_hash']; hash(passwordGuess, hashCallback); } function hashCallback(passwordGuessHash) { callback(passwordHash === passwordGuessHash); } } 这种写法更啰嗦一些,但读起来更清晰,也更容易扩展。由于这里赋予了异步结果(即passwordHash)更宽广的作用域,所以获得了更大的灵活性。 按照惯例,请避免两层以上的函数嵌套。关键是找到一种在激活异步调用之函数的外部存储异步结果的方式,这样回调本身就没有必要再嵌套了。 如果这样听起来有点诘聱难懂,请别担心。我们在后续几章中会看到大量的异步事件例子,那里的异步事件顺序运行且没有嵌套式事件处理器。