回调地狱

什么是回调地狱?

异步JavaScript或使用回调的JavaScript很难直观地得到正确的结果。很多代码最终看起来像这样:

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

看到最后的金字塔形状和所有})?伊克!这被亲切地称为回调地狱。
回调地狱的原因是,当人们试图以一种从上到下的视觉方式执行JavaScript的方式编写JavaScript时。很多人犯这个错误!在C,Ruby或Python等其他语言中,期望第1行发生的任何事情都会在第2行的代码开始运行之前完成,依此类推。正如你将会学到的,JavaScript是不同的

什么是回调函数?

回调只是使用JavaScript函数的惯例的名称。 JavaScript语言中没有特别的东西叫做“回调”,它只是一个约定。不像大多数函数那样立即返回一些结果,使用回调函数需要一些时间来产生结果。 “异步”这个词,又名“异步”,意思是“需要一些时间”或“将来会发生,而不是现在”。通常回调仅在进行I / O时使用,例如下载东西,阅读文件,与数据库交互等

当你调用一个普通的函数时,你可以使用它的返回值

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

然而,异步和使用回调的函数不会立即返回任何内容

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

怎么解决回调地狱?

1.保持你的代码简短
这里有一些凌乱的浏览器JavaScript,它使用浏览器请求向服务器发送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
  })
}

正如你所看到的,命名函数非常简单并且有一些直接的好处

  • 由于描述性功能名称,使代码更容易阅读
  • 当发生异常时,你将获得引用实际函数名称而不是“匿名”的堆栈跟踪
  • 允许你移动功能并按名称引用它们

现在我们可以将这些功能移到我们程序的顶层

   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. 模块化
这是最重要的部分:任何人都有能力创建模块(又名图书馆)。引用(node.js项目的)Isaac Schlueter的话:“编写一个小模块,每个模块都做一件事,然后将它们组装成其他模块,做更大的事情。如果你不去那里,你不能进入回调地狱
让我们从上面取出样板代码,并将其分成几个文件,将其转换为模块。我将展示一个适用于浏览器代码或服务器代码的模块模式(或者适用于两者的代码)
这是一个名为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函数而陷入困境
  • ormuploader可以在其他地方使用,无需复制代码,并且可以轻松地在github或npm上共享

3.处理每一个错误
有不同类型的错误:由程序员造成的语法错误(通常在你尝试首次运行程序时发生),程序员造成的运行时错误(代码已运行但存在导致某些事情混乱的错误),平台错误由无用的文件权限,硬盘驱动器故障,无网络连接等引起的。这部分只是为了解决最后一类错误
前两条规则主要是关于让你的代码可读,但这是关于让代码稳定的。在处理回调时,你根据定义处理已分派的任务,请在后台执行某些操作,然后成功完成或由于失败而中止。任何有经验的开发人员都会告诉你,你永远无法知道这些错误何时发生,所以你必须对它们进行计划

通过回调,处理错误的最常见方法是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
 }

有第一个参数是错误是一个简单的惯例,鼓励你记住处理你的错误。如果它是第二个参数,你可以编写像函数handleFile(file){}的代码,并且更容易忽略错误
代码库也可以配置为帮助您记住处理回调错误。最简单的使用称为标准。你所要做的就是在你的代码文件夹中运行$ standard,它会向你显示你的代码中的每一个回调,并带有未处理的错误

概要

不要嵌套功能。给他们姓名并将他们放在程序的顶层

  1. 利用函数提升来利用你的优势来移动函数
  2. 处理每个回调中的每一个错误。使用标准来帮助你
  3. 创建可重用的函数并将它们放在模块中以减少理解代码所需的认知负载。将代码分割成小块这样也可以帮助您处理错误,编写测试,强制您为您的代码创建稳定且文档化的公共API,并有助于重构

避免回调地狱的最重要的方面是将功能移开,以便程序流程可以更容易理解,而无需新手参与功能的所有细节以了解程序正在尝试做什么
你可以先将函数移动到文件底部,然后使用require(’./ photo-helpers.js’)等相关需求将它们移动到另一个文件中,然后将它们移动到独立模块like require(‘image-resize’))

以下是创建模块时的一些经验法则:

  • 首先将重复使用的代码移入一个函数
  • 当你的函数(或与同一主题相关的一组函数)变得足够大时,将它们移动到另一个文件中并使用module.exports将其公开。你可以使用相对需求来加载它
  • 如果你有一些可以在多个项目中使用的代码,给它自己的readme,tests和package.json,并将它发布到github和npm。这里列出的具体方法有太多令人敬畏的好处
  • 一个好的模块很小,专注于一个问题
  • 模块中的单个文件不应超过150行左右的JavaScript
  • 一个模块不应该有多于一个嵌套文件夹级别的文件夹。如果是这样,它可能做了太多事情
  • 请你认识的更有经验的编程人员向你展示优秀模块的例子,直到你对他们的样子有了一个好的想法。如果需要花费几分钟时间才能了解正在发生的事情,那么它可能不是一个很好的模块

总结

回调地狱最主要的就是因为功能逻辑代码嵌套的层次太多,导致可读性降低,维护困难,避免回调地狱的最重要的方面是将功能移开,保持代码简单,不嵌套并分成小模块,也就是多多进行代码封装,将你所要的属性和方法用function关键字包裹起来,而且还要给它取一个有意义的名字,例如:页面上弹框,显示,隐藏,下拉等各个功能小模块,分别用有名函数给包裹起来,少用匿名函数,以便可以重复的多次使用,这也是可以便于程序流程的理解
除了常见的一种回调函数作为异步处理,还有promises,Generators,async是处理异步处理的方式,关于这三个我也在学习当中,理论的东西虽是概念,没有大量代码的编写,个人觉得是很难理解这些东西,但是代码就是这些语言文字实实在在的转化,骚年们,加油,加油…

原文出处:https://www.jianshu.com/p/39adf6ab8ad1

你可能感兴趣的:(Javascript,回调函数,Javascript)