前端经验分享

谈一下异步编程模型的一些优化(翻译)

2018-11-16  本文已影响14人  LucasLight

tips

翻译的文章来自于 http://callbackhell.com/ 翻译过程中有掺杂个人的理解和翻译语法问题,如果英语质量过关,还请阅读原文。

废话不多说,先来看一下回调地狱

Asynchronous JavaScript, or JavaScript that uses callbacks, is hard to get right intuitively. A lot of code ends up looking like this
异步的JavaScript或者JavaScript使用了回调函数,是很难按照正确的顺序来直观的阅读的,有很多代码看起来像是下面这样子

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(dest + 'w' + width + '_' + filename, function(err) {
              //第五层回调
              if (err) console.log('Error writing file: ' + err)
            })
          }.bind(this))
        }
      })
    })
  }
})

如何避免回调地狱?

保持你的代码更加简单

下面使用ajax请求来做为举例说明:

button.onclick = function (submitEvent) {
  var name = document.querySelector('input').value
  request({
    uri: "http://*.com/upload",
    body: name,
    method: "POST"
  }, function (err, response, body) {
//成功之后的执行回调函数
    var statusMessage = document.querySelector('.status')
    if (err) return statusMessage.value = err
    statusMessage.value = body
  })
}

其实你可以发现,这里面有两个匿名函数,给匿名函数加上名字会清晰很多

// 在这里为方法添加一个命名,submitForm 提交方法
button.onclick = function submitForm(submitEvent) {
  var name = document.querySelector('input').value
  request({
    uri: "http://*.com/upload",
    body: name,
    method: "POST"
  }, 
// http响应方法处理
function httpResponse(err, response, body) {
//成功之后的执行回调函数
    var statusMessage = document.querySelector('.status')
    if (err) return statusMessage.value = err
    statusMessage.value = body
  })
}

添加了方法明明之后,你可以比较直观的去使用这个方法,有几点好处:

  1. 函数命名之后,代码更加容易阅读
  2. 当一个异常发生的时候,你可以通过栈读出来,而不是一个匿名函数(anonymous function)
  3. 允许你在其他地方重命名你的函数,并且可以引用

现在我们可以移动你的已经命名的方法:

document.querySelector('form').onsubmit = formSubmit
// 表单提交方法
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
}

注意: function 声明虽然是定义在文件的底部,调用确实在上面,这都要感谢JavaScript的方法提升的特性[https://gist.github.com/maxogden/4bed247d9852de93c94c]

模块化

模块化最重要的就是:任何人都可以创建模块(aka libraries),引用node.js项目组的一句话就是:
编写每个模块做一件事情,然后将它们组装成其他模块做一件更大的事情。如果你不去那里,你就不会进入回调地狱。
我们再来看一下之前的回调方法:现在封装到一个formUploader.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
}

然后我们就可以调用方法类似于这样子:

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

现在你的代码就只剩下了两行

认真的处理回调过程中的每一个错误

错误有很多种,包括语法错误,运行时错误,平台错误之类,我们应当灵活的处理所有的bug。
前面的两个规则(保证代码简介、模块化)是为了保证代码更加直观,而这个规则是保证你的代码更加具有稳定性。
伴随着回调函数,node.js绝大多数的处理方式就是error优先返回,

 var fs = require('fs')

 fs.readFile('/Does/not/exist', handleFile)

 function handleFile (error, file) {
   if (error) return console.error('Uhoh, there was an error', error)
   // otherwise, continue on and use `file` in your code
 }

错误优先返回,是一种能让你记住处理错误的简单的方式,如果有第二个参数,你可以写一个function来处理,也可以更加简单的处理错误。

总结

  1. 不要随便使用匿名函数,给他们一个名字会好很多(最好是封装在你的程序的顶部)
  2. 使用方法提升来让你的方法更加优先定义。
  3. 在你的每一个回调方法中都单独处理每一个错误,使用标准化来规范自己的代码风格
  4. 创建一个可复用的方法,并且放入到module(模块)里面,让你的代码更加可读。分割你的代码到每一个小的方法中,可以更好分片的处理你的错误(error)。强迫你必须创建一个稳定且开放的代码API来约束你的代码,这对于以后的重构会有很大的帮助。

其实整体来讲,回调地狱真正恐怖的地方在于逻辑无法清晰的在代码层面读取出来,我们通过为方法命名,抽取出来,并且模块化可以更好地理解并且使用你的回调方法。

你也可以把方法(你想要重构的)抽取出来,放在文件的最下面(不会碍着你的正常看代码的风格),然后逐步的把文件都移动到其他文件中去,你也可以直接新建一个require(./helpper.js),例如这样子,然后再把你的方法逐步的重构出去。

创建模块的事后有一个规则要注意

上一篇下一篇

猜你喜欢

热点阅读