美文网首页前端经验分享
谈一下异步编程模型的一些优化(翻译)

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

作者: LucasLight | 来源:发表于2018-11-16 14:02 被阅读14次

    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;
    

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

    • 更好的开发 代码理解能力 ,开发者不需要全部理解formuploader.submit方法,就可以了
    • 代码可以更好地被复用在npm或者github上

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

    错误有很多种,包括语法错误,运行时错误,平台错误之类,我们应当灵活的处理所有的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),例如这样子,然后再把你的方法逐步的重构出去。

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

    • 首先是封装你最常见的重复代码逻辑
    • 当你的方法(或者一组与方法相关的功能)变得足够多(多到你觉得可以封装功能的时候)移动他们到另外一个文件并且导出模块(module.exports),你可以使用相对路径加载这模块。
    • 如果你有相同的代码在多个文件中,给他们相对应的readme.md然后测试用例以及package.json,最后发布到npm上面,在npm上已经有巨多无比的模块给你去用。
    • 一个好的模块是足够小巧,且能够专注于解决问题的。
    • 一个模块中,每一个文件都不应当超出150行左右的代码,否则要认真思考一下自己的业务逻辑是否有这么多复杂的代码。
    • 一个模块不应当有更多两层以上的存放JavaScript文件的文件夹,否则你就要思考一下,你究竟是在解决什么问题。
    • 想一个更加有经验的开发者来请教学习如何更好的封装一个模块,一直到在他们看起来你比他们有更好的想法。如果一个模块需要你话费好几分钟才能够理解他到底是能够做什么,那么他可能不是一个好的模块。

    相关文章

      网友评论

        本文标题:谈一下异步编程模型的一些优化(翻译)

        本文链接:https://www.haomeiwen.com/subject/uvipfqtx.html