11月22, 2013

[译]回调地狱

——译自《Callback Hell

这是一篇有关编写异步javascript程序的指南

什么是“回调地狱”?

异步js代码,或者用到了回调函数的js代码,都很难直观理解。这类代码看起来大多数像这样:

fs.readdir(source, function(err, files) {
  if (err) {
    console.log('Error finding files: ' + err)
  } else {
    files.forEach(function(filename, fileIndex) {
      console.log(filename)
      gm(source + filename).size(function(err, values) {
        if (err) {
          console.log('Error identifying file size: ' + err)
        } else {
          console.log(filename + ' : ' + values)
          aspect = (values.width / values.height)
          widths.forEach(function(width, widthIndex) {
            height = Math.round(width / aspect)
            console.log('resizing ' + filename + 'to ' + height + 'x' + height)
            this.resize(width, height).write(destination + 'w' + width + '_' + filename, function(err) {
              if (err) console.log('Error writing file: ' + err)
            })
          }.bind(this))
        }
      })
    })
  }
})

艾玛,到处都是function})!是不是很眼熟?这就是我们“亲(坑)爱(爹)的”回调地狱

其实,只需要知道以下一些小技巧,妈妈就再也不用担心你写不出直观易懂的代码:

给你的函数命名

下面是一段略显杂乱的浏览器js代码,这段代码利用browser-request来向服务端发送AJAX请求:

var form = document.querySelector('form');
form.onsubmit = function(submitEvent) {
  var name = document.querySelector('input').value
  request({
    uri: "http://example.com/upload",
    body: name,
    method: "POST"
  }, function(err, response, body) {
    var statusMessage = document.querySelector('.status')
    if (err) return statusMessage.value = err
    statusMessage.value = body
  })
}

这段代码包含了两个匿名函数,现在我们给它们命名。

var form = document.querySelector('form')
form.onsubmit = function formSubmit(submitEvent) {
  var name = document.querySelector('input').value
  request({
    uri: "http://example.com/upload",
    body: name,
    method: "POST"
  }, function postResponse(err, response, body) {
    var statusMessage = document.querySelector('.status')
    if (err) return statusMessage.value = err
    statusMessage.value = body
  })
}

你看,给函数命名其实非常简单,而且这样做还能让你的代码达到意想不到的效果。

  • 代码更易读
  • 代码抛异常的时候,你能很快定位到抛错的函数名,而不是定位到“匿名”函数
  • 使代码层级变浅,至少不会出现很深的嵌套关系,这也是我下面要讲的:

保持浅的代码风格

在上述例子的基础上,我们进一步改掉上面代码里的三级嵌套:

function formSubmit(submitEvent) {
  var name = document.querySelector('input').value
  request({
    uri: "http://example.com/upload",
    body: name,
    method: "POST"
  }, postResponse)
}

function postResponse(err, response, body) {
  var statusMessage = document.querySelector('.status')
  if (err) return statusMessage.value = err
  statusMessage.value = body
}

document.querySelector('form').onsubmit = formSubmit

这样的代码就好看多了,而且以后不管是编写,重构或者是拆分都会更加容易哦。

模块化

现在是最重要的环节: 任何人都能创建模块(也就是代码库)。引用Isaac Schlueter(node.js项目的成员)的一句话:“一个小模块只需要实现一个单一功能,然后把小模块们组装到其它要实现更大功能的模块中。只要你稍微注意就不会陷入回调地狱。”

现在我们把上面的示例代码提取出来,通过将其拆成几个文件来创建一个模块。既然得同时编写浏览器端和服务端的JavaScript,我下面展示的这种方式能让代码在浏览器和服务端都可以运行,而且代码还超简洁。

下面这个formuploaded.js是一个新文件,包含了我们之前写的那两个函数:

function formSubmit(submitEvent) {
  var name = document.querySelector('input').value
  request({
    uri: "http://example.com/upload",
    body: name,
    method: "POST"
  }, postResponse)
}

function postResponse(err, response, body) {
  var statusMessage = document.querySelector('.status')
  if (err) return statusMessage.value = err
  statusMessage.value = body
}

exports.submit = formSubmit

代码最后的 exports是 CommonJS 模块系统的一个例子,编写服务端 javascript程序的Node.js就用到了这个。我非常喜欢这种模块化的方式,因为它真的很简单 —— 你只需要定义一些接口,当其它地方加载这个模块时就能够调用了(这就是exports的好处)。

要在浏览器里使用这些CommonJS模块时,你可以借助一个叫browserify的命令行工具。我在这儿就不详细教大家怎么用了,总之借助这个工具你就能用require方式加载模块代码到你的程序里。

现在我们有了formuploader.js文件(并且它已经通过script标签加载到页面里了),我们只需要require这个文件就能使用它提供的方法了,我们的应用程序代码就变成了下面这个样子:

var formUploader = require('formuploader')
document.querySelector('form').onsubmit = formUploader.submit

现在我们的应用程序只有两行代码了,并且多了以下好处:

  • 新手也能轻松看懂代码 —— 他们不会因为阅读整个formuploader的函数而卡壳。
  • formuploader 不必拷贝代码就可以用在任何地方,而且可以在github上轻松地分享给大家。
  • 代码本身也很优美,简单易读。 在web浏览器和服务端有很多模块模式,有些很复杂。我在本文中展示的模块方式是我认为最最容易理解的。

我还是不太理解本文

可以去读一下我的回调简介

什么是promises?

Promises是一个处理Javascript中异步编程的更抽象的模式。

这篇文章旨在展示如何编写优美的javascript代码。如果你使用了第三方库来抽象化你的JS代码,那你得保证你愿意推动所有参与你的项目的人都跟你保持一样的对JS的看法。

我的个人经历是,在90%的异步代码中我都用了回调方式,当代码变得复杂时我就尝试引入一些第三方库,比如async来收拾烂摊子。

话虽如此,每个开发者都有其独特的开发风格,你也应尽量保持你习惯的风格。你只需记住事无绝对:有些人就只喜欢用回调方式,有些人却不是。

补充阅读

有兴趣的话你可以在github上fork这个项目!欢迎贡献新内容或者修正已有内容。

本文链接:https://75team.com/post/译回调地狱.html

-- EOF --

Comments

评论加载中...

注:如果长时间无法加载,请针对 disq.us | disquscdn.com | disqus.com 启用代理。