【翻译】关于回调地狱

回调地狱

JavaScript异步递次誊写指南

什么是“回调地狱”?

我们很难一眼就看懂异步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))
        }
      })
    })
  }
})

这个一堆以})末端的金字塔,我们很亲热地称它为——“回调地狱”。

之所以会涌现回调地狱,是由于我们写JavaScript平常是视觉上的从上到下誊写。很多人犯了这个毛病!在比方C、Ruby或许Python等其他言语,在第二行代码运转之前,第一行代码一定已运转完了。然则如背面所说的,JavaScript是差别的。

什么是回调函数?

回调函数是JavaScript里约定俗成的一个称号。实际上并不存在肯定的“回调函数”,只是人人就管谁人位置的函数作回调函数。与大多数运转后马上给出效果的函数差别,运用回调的函数要花一些时刻才得出效果。“异步”这个词就是代表‘要花时刻,未来运转’。通常回调函数会用鄙人载文件、读取文件、或许数据库相干事宜等。

当你挪用一个一般函数,你能够马上获得它的值:

var result = multiplyTwoNumbers(5, 10)
console.log(result)
// 50 gets printed out

而运用回调的函数不能马上获得反应。

var photo = downloadPhoto('http://coolcats.com/cat.gif')
// photo is 'undefined'!

这个时刻,这张gif能够要下载良久,你总不能让递次什么都不干停下来就等它下载完。

相反,你能够贮存下载完后触发的代码到一个函数里,这就是回调函数!把这些代码写进downloadPhoto函数,下载胜利后,会运转回调函数。

downloadPhoto('http://coolcats.com/cat.gif', handlePhoto)

function handlePhoto (error, photo) {
  if (error) console.error('Download error!', error)
  else console.log('Download finished', photo)
}

console.log('Download started')

我们明白回调最难的处所就是明白递次的运转递次。例子中发作了三个主要事宜,首先是handlePhoto函数被声明,然后作为回调函数被downloadPhoto函数挪用,末了控制台打印出'Download started'

注重handlePhoto还没有被挪用,它只是被建立然后最为回调函数传入downloadPhoto。直到downloadPhoto完成下载,他都不会运转。

这个例子申明两个题目:

  • handlePhoto(回调函数)只是贮存了将要运转的东西

  • 不要从上到下浏览递次,递次会依据事变完成而跳转

怎样修复回调地狱?

你只需要随着一下三步走:

1.削减代码嵌套

以下是一些用于AJAX的浏览器端代码(运用browser-request):

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
  })
}

你们看,给函数定名很简朴,然则优点可不少:

  • 有了函数名,能够很轻易晓得这段代码的作用

  • 在控制台调试失足的时刻,控制台会通知你是哪一个函数失足了,而不是一个匿名函数(anonymous)

  • 能够让你把这些函数挪动到适宜的位置,运用的时刻用函数名挪用就可以够了

如今我们都写到递次最外层:

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
}

注重,函数声明在底部,却依然能挪用,这得益于函数提拔

2.模块化

用上面的例子,我们将把它拆分红多个文件,我会通知你怎样把他做成模块。

建立一个包括前面两个函数的新文件formuploader.js

module.exports.submit = 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
}

module.exports来自node.js的模块体系,能够运用在node、Electron,浏览器上(借助browserify)。我非常喜好这类作风,由于哪儿都能用,而且易于明白,不必依赖于其他庞杂设置。

我们获得了formuploader.js,只需引入并运用它就可以够了!操纵以下:

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

如今我们的代码只要两行,有以下优点:

  • 易于新开发者明白,他们不会为读取一切的formuploader函数而陷入困境。

  • formuploader不必复制粘贴代码,只需在github或许npm下载分享的代码就可以够了。

3.处置惩罚每一个毛病

罕见毛病有几种

  • 语法毛病(运转失利)

  • 运转时毛病(能够运转然则有bug)

  • 平台毛病(文件权限题目、磁盘题目、收集题目)

前两条划定规矩重假如进步你的代码的可读性,而这条是让你的代码更稳固。在处置惩罚回调时,您将依据定义处置惩罚发送的使命,在背景实行某些操纵,末了胜利完成或失利中断。任何有履历的开发人员都邑通知你,你永久不会晓得这些毛病发作什么时刻发作,所以在题目涌现时都必需有所对策。

最常常使用的回调毛病处置惩罚是Node.js作风,也就是回调函数的第一个参数老是毛病参数。

 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
 }

第一个参数是error是一个简朴的共鸣,如许做能够提示你必需处置惩罚你的毛病。假如是第二个参数的话你很轻易把代码写成function handleFile (file) { }然后就忘了处置惩罚毛病。
代码规范化东西也能够提示你添加回调毛病处置惩罚,最简朴的要领之一是运用standard。只是在你的文件目次运转 $ standard就可以搜检你的代码有无缺乏毛病处置惩罚。

总结

  1. 不要嵌套函数,定名后挪用更好

  2. 运用函数提拔

  3. 处置惩罚回调函数的每一个毛病

  4. 建立可重用函数,写成模块,让你更轻易读懂代码。把你的代码拆分红小块能够协助你处置惩罚毛病,写测试,重构,轻易为你的代码写更稳固的API

防备回调地狱的最主要的是挪动函数,以便递次流程能够更轻易地被明白,其他递次员能够不翻遍全部文件就可以晓得这段递次的功用。

你能够先把函数挪动到底部,然后逐步把函数写到模块文件里,然后运用require引入它(就像援用其他npm模块一样)。

一些写模块的履历:

  • 先把常常重复运用的功用写成一个函数

  • 当这个函数写得够大以后,把他挪动到另一个文件,用module.exports暴露它,然后用require引入

  • 假如你的代码是通用的,能够写readme文件和package.json宣布到npm或许github

  • 一个好模块,体积要小,而且针对只一个题目

  • 模块中的单个文件不该凌驾约150行

  • 模块不该该有多个级别的嵌套文件夹,个中包括JavaScript文件。假如是如许,它能够做的太多了

  • 让有履历的递次员引见你一些好用的模块,尝试明白这个模块的功用,假如花了几分钟的话,这个模块能够就不够好了

关于promise/生成器/ES6?

在检察更高等的解决方案之前,请记着,回调是JavaScript的一个基础部份(由于它们只是函数),你应当进修怎样读写它们,然后再转向更高等的言语功用,由于它们依赖于对回调的明白。假如您还不能写可保护的回调代码,请继承努力进修!

假如你真的想你的异步代码能够“从上至下浏览”,你能够尝尝这些美好的要领。注重,这些功用在差别平台会有兼容性题目,运用前请先观察清晰!

Promise就是一种让你从上至下写回调函数的要领,它勉励你运用try/catch处置惩罚更多范例的毛病。

Generator能够让你“停息”一个函数(而不停息全部递次),它也能你从上至下写异步函数,然则价值是代码有点庞杂难以明白。wat就是运用这个要领。

Async functions是ES7的特征,是生成器和promise更高等的封装,有兴致本身谷歌一下呗。

就我个人而言,我运用回调函数处置惩罚90%的异步代码,当事变变得庞杂时,依托一些库,比方run-parallel或许run-series。我不认为研讨回调 vs promise vs 其他什么要领对我来讲有什么协助,最主要的照样坚持代码简朴,不嵌套,并分红小模块。

不管你挑选何种要领,请一直处置惩罚每一个毛病,并坚持代码简约

记着,只要能够防备回调地狱和丛林火警。

原文: http://callbackhell.com/
本文github地点: https://github.com/ssshooter/…

    原文作者:ssshooter
    原文地址: https://segmentfault.com/a/1190000009644973
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞