关于回调地狱

回调地狱

什么是“回调地狱”?
我们很难一眼就看懂异步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')

这个时候,这张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就能检查你的代码有没有缺少错误处理。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
资源包主要包含以下内容: ASP项目源码:每个资源包都包含完整的ASP项目源码,这些源码采用了经典的ASP技术开发,结构清晰、注释详细,帮助用户轻松理解整个项目的逻辑和实现方式。通过这些源码,用户可以学习到ASP的基本语法、服务器端脚本编写方法、数据库操作、用户权限管理等关键技术。 数据库设计文件:为了方便用户更好地理解系统的后台逻辑,每个项目都附带了完整的数据库设计文件。这些文件通常包括数据库结构图、数据表设计文档,以及示例数据SQL脚本。用户可以通过这些文件快速搭建项目所需的数据库环境,并了解各个数据表之间的关系和作用。 详细的开发文档:每个资源包都附有详细的开发文档,文档内容包括项目背景介绍、功能模块说明、系统流程图、用户界面设计以及关键代码解析等。这些文档为用户提供了深入的学习材料,使得即便是从零开始的开发者也能逐步掌握项目开发的全过程。 项目演示与使用指南:为帮助用户更好地理解和使用这些ASP项目,每个资源包都包含项目的演示文件和使用指南。演示文件通常以视频或图文形式展示项目的主要功能和操作流程,使用指南则详细说明了如何配置开发环境、部署项目以及常见问题的解决方法。 毕业设计参考:对于正在准备毕业设计的学生来说,这些资源包是绝佳的参考材料。每个项目不仅功能完善、结构清晰,还符合常见的毕业设计要求和标准。通过这些项目,学生可以学习到如何从零开始构建一个完整的Web系统,并积累丰富的项目经验。
资源包主要包含以下内容: ASP项目源码:每个资源包都包含完整的ASP项目源码,这些源码采用了经典的ASP技术开发,结构清晰、注释详细,帮助用户轻松理解整个项目的逻辑和实现方式。通过这些源码,用户可以学习到ASP的基本语法、服务器端脚本编写方法、数据库操作、用户权限管理等关键技术。 数据库设计文件:为了方便用户更好地理解系统的后台逻辑,每个项目都附带了完整的数据库设计文件。这些文件通常包括数据库结构图、数据表设计文档,以及示例数据SQL脚本。用户可以通过这些文件快速搭建项目所需的数据库环境,并了解各个数据表之间的关系和作用。 详细的开发文档:每个资源包都附有详细的开发文档,文档内容包括项目背景介绍、功能模块说明、系统流程图、用户界面设计以及关键代码解析等。这些文档为用户提供了深入的学习材料,使得即便是从零开始的开发者也能逐步掌握项目开发的全过程。 项目演示与使用指南:为帮助用户更好地理解和使用这些ASP项目,每个资源包都包含项目的演示文件和使用指南。演示文件通常以视频或图文形式展示项目的主要功能和操作流程,使用指南则详细说明了如何配置开发环境、部署项目以及常见问题的解决方法。 毕业设计参考:对于正在准备毕业设计的学生来说,这些资源包是绝佳的参考材料。每个项目不仅功能完善、结构清晰,还符合常见的毕业设计要求和标准。通过这些项目,学生可以学习到如何从零开始构建一个完整的Web系统,并积累丰富的项目经验。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值