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

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文件的文件夹,否则你就要思考一下,你究竟是在解决什么问题。
  • 想一个更加有经验的开发者来请教学习如何更好的封装一个模块,一直到在他们看起来你比他们有更好的想法。如果一个模块需要你话费好几分钟才能够理解他到底是能够做什么,那么他可能不是一个好的模块。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,539评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,911评论 3 391
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,337评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,723评论 1 290
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,795评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,762评论 1 294
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,742评论 3 416
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,508评论 0 271
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,954评论 1 308
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,247评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,404评论 1 345
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,104评论 5 340
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,736评论 3 324
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,352评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,557评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,371评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,292评论 2 352

推荐阅读更多精彩内容

  • 说起爱孩子,我想所有的父母都会回答:我当然是爱他们的呀!是的,我们都很爱我们的孩子,恨不能倾其所有,掏出自己的心肺...
    佩佩_轻剪时光阅读 526评论 1 0
  • 8月31日 升入初一,不开心…… 今天我好像不开心,上初一了,最紧张的莫过于是分班了,谁都不想和自己的好朋友分开,...
    Sernedipity阅读 160评论 0 0
  • 何田田是个藏不住话的人,大家都这么说。 确实也是,她就像是夏夜里稻田里的青蛙一样,呱呱呱个不停,还是没冬没夏地呱。...
    春风巷72号阅读 365评论 7 6
  • 一副精致的耳钉耳线耳环可以给小仙女们的造型加分不少,无论是外出、上班、约会、聚会,小小的耳环在发际线间一闪,都会让...
    小淘米_TTMIX阅读 1,507评论 0 0