Node.js 十大常见的开发者错误( 二 )


function handleLogin(..., done) { db.User.get(..., function(..., user) {if(!user) {return done(null, ‘failed to log in’)}utils.verifyPassword(..., function(..., okay) {if(okay) {return done(null, ‘failed to log in’)}session.login(..., function() {done(null, ‘logged in’)})}) })}复制代码越复杂的任务,这个的坏处就越大 。像这样嵌套回调函数,我们的程序很容易出错,而且代码难以阅读和维护 。一个权宜之计是把这些任务声明为一个个的小函数,然后再将它们联系起来 。不过,(有可能是)最简便的解决方法之一是使用一个 Node.js 公共组件来处理这种异步 js,比如 Async.js:
function handleLogin(done) { async.waterfall([function(done) {db.User.get(..., done)},function(user, done) {if(!user) {return done(null, ‘failed to log in’)}utils.verifyPassword(..., function(..., okay) {done(null, user, okay)})},function(user, okay, done) {if(okay) {return done(null, ‘failed to log in’)}session.login(..., function() {done(null, ‘logged in’)})} ], function() {// ... })}复制代码Async.js 还提供了很多类似“async.waterfall” 的方法去处理不同的异步场景 。为了简便起见,这里我们演示了一个简单的示例,实际情况往往复杂得多 。
(打个广告,隔壁的《ES6 Generator 介绍》提及的 Generator 也是可以解决回调地狱的哦,而且结合 Promise 使用更加自然,请期待隔壁楼主的下篇文章吧:D)
错误4:期待回调函数同步执行使用回调函数的异步程序不只是 JavaScript 和 Node.js 有,只是它们让这种异步程序变得流行起来 。在其他编程语言里,我们习惯了两个语句一个接一个执行,除非两个语句之间有特殊的跳转指令 。即使那样,这些还受限于条件语句、循环语句以及函数调用 。
然而在 JavaScript 里,一个带有回调函数的方法直到回调完成之前可能都无法完成任务 。当前函数会一直执行到底:
function testTimeout() { console.log(“Begin”) setTimeout(function() {console.log(“Done!”) }, duration * 1000) console.log(“Waiting..”)}复制代码你可能会注意到,调用“testTimeout” 函数会先输出“Begin”,然后输出“Waiting..”,紧接着几秒后输出“Done!” 。
任何要在回调函数执行完后才执行的代码,都需要在回调函数里调用 。
错误5:给“exports” 赋值,而不是“module.exports”Node.js 认为每个文件都是一个独立的模块 。如果你的包有两个文件,假设是“a.js” 和“b.js”,然后“b.js” 要使用“a.js” 的功能,“a.js” 必须要通过给 exports 对象增加属性来暴露这些功能:
// a.jsexports.verifyPassword = function(user, password, done) { ... }复制代码完成这步后,所有需要“a.js” 的都会获得一个带有“verifyPassword” 函数属性的对象:
// b.jsrequire(‘a.js’) // { verifyPassword: function(user, password, done) { ... } } 复制代码然而,如果我们想直接暴露这个函数,而不是让它作为某些对象的属性呢?我们可以覆写 exports 来达到目的,但是我们绝对不能把它当做一个全局变量:
// a.jsmodule.exports = function(user, password, done) { ... }复制代码注意到我们是把“exports” 当做 module 对象的一个属性 。“module.exports” 和“exports” 这之间区别是很重要的,而且经常会使 Node.js 新手踩坑 。
错误6:从回调里抛出错误JavaScript 有异常的概念 。在语法上,学绝大多数传统语言(如 Java、C++)对异常的处理那样,JavaScript 可以抛出异常以及在 try-catch 语句块中捕获异常:
function slugifyUsername(username) { if(typeof username === ‘string’) {throw new TypeError(‘expected a string username, got '+(typeof username)) } // ...} try { var usernameSlug = slugifyUsername(username)} catch(e) { console.log(‘Oh no!’)}复制代码然而,在异步环境下,tary-catch 可能不会像你所想的那样 。比如说,如果你想用一个大的 try-catch 去保护一大段含有许多异步处理的代码,它可能不会正常的工作:
try { db.User.get(userId, function(err, user) {if(err) {throw err}// ...usernameSlug = slugifyUsername(user.username)// ... })} catch(e) { console.log(‘Oh no!’)}复制代码如果“db.User.get” 的回调函数异步执行了,那么 try-catch 原来所在的作用域就很难捕获到回调函数里抛出的异常了 。
这就是为什么在 Node.js 里通常使用不同的方式处理错误,而且这使得所有回调函数的参数都需要遵循 (err, ...) 这种形式,其中第一个参数是错误发生时的 error 对象 。


推荐阅读