从前一文中你真的了解回调?我们已知道回调函数是必须得依赖另一个函数执行调用,它是异步执行的,也就是需要时间等待,典型的例子就是Ajax应用,比如http请求,在不刷新浏览器的情况下,当你执行DOM事件时,比如页面上点击某链接,回车等事件操作,浏览器会悄悄向服务端发送若干http请求,携带后台可识别的参数,等待服务器响应返回数据,这个过程是异步回调的,当许多功能需要连续调用,环环相扣依赖时,它就类似下面的代码,代码全部一层一层的嵌套,看起来就很庞大,很恶心,就产生了回调地狱.本文,将为你揭晓怎么避免回调地狱,您将在本文中了解到以下内容:
什么是“回调地狱”?
异步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的方式编写JavaScript时。很多人犯这个错误,在C,Ruby或Python等其他语言中,期望第1行发生的任何事情都会在第2行的代码开始运行之前完成,依此类推。正如你将会学到的,JavaScript是不同的
什么是回调函数?
回调只是使用JavaScript函数的惯例的名称。 JavaScript语言中没有特别的东西叫做“回调”,它只是一个约定。不像大多数函数那样立即返回一些结果,使用回调函数需要一些时间来产生结果。 “异步”这个词,又名“异步”,意思是“需要一些时间”或“将来会发生,而不是现在”。通常回调仅在进行I / O时使用,例如下载东西,阅读文件,与数据库交互等
当你调用一个普通的函数时,你可以使用它的返回值
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函数并传递handlePhoto作为其回调函数,最后打印出“Download started”
请注意,handlePhoto尚未被调用,它只是被创建并作为回调传入downloadPhoto。但直到downloadPhoto完成其任务后才能运行,这可能需要很长时间,具体取决于Internet连接的速度
这个例子是为了说明两个重要的概念
我该如何解决回调地狱?
回调地狱是由于糟糕的编码习惯造成的。幸运的是,编写更好的代码并不困难! 你只需遵循三条规则:
这里有一些凌乱的浏览器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
}
请注意,这里的函数声明是在文件底部定义的。这要归功于提升功能
这是最重要的部分:任何人都有能力创建模块。引用(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
现在我们的应用程序只有两行代码,并具有以下优点:
有不同类型的错误:由程序员造成的语法错误(通常在你尝试首次运行程序时发生),程序员造成的运行时错误(代码已运行但存在导致某些事情混乱的错误),平台错误由无用的文件权限,硬盘驱动器故障,无网络连接等引起的。这部分只是为了解决最后一类错误
前两条规则主要是关于让你的代码可读,但这是关于让代码稳定的。在处理回调时,你根据定义处理已分派的任务,请在后台执行某些操作,然后成功完成或由于失败而中止。任何有经验的开发人员都会告诉你,你永远无法知道这些错误何时发生,所以你必须对它们进行计划
通过回调,处理错误的最常见方法是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,它会向你显示你的代码中的每一个回调,并带有未处理的错误
避免回调地狱的最重要的方面是将功能移开,以便程序流程可以更容易理解,而无需新手参与功能的所有细节以了解程序正在尝试做什么
你可以先将函数移动到文件底部,然后使用require('./ photo-helpers.js')等相关需求将它们移动到另一个文件中,然后将它们移动到独立模块like require('image-resize'))
以下是创建模块时的一些经验法则:
承诺/生成器/ES6等呢
在研究更先进的解决方案之前,请记住,回调是JavaScript的基本组成部分(因为它们只是函数),你应该在学习更先进的语言特性之前学习如何读写它们,因为它们都依赖于对回调。如果你还不能编写可维护的回调代码,请继续使用它
如果你真的希望你的异步代码从头到尾阅读,你可以尝试一些奇特的东西。请注意,这些可能会引入性能和/或跨平台运行时兼容性问题
回调地狱最主要的就是因为功能逻辑代码嵌套的层次太多,导致可读性降低,维护困难,避免回调地狱的最重要的方面是将功能移开,也就是多多进行代码封装,将你所要的属性和方法用function关键字包裹起来,而且还要给它取一个有意义的名字,例如:页面上弹框,显示,隐藏,下拉等各个功能小模块,分别用有名函数给包裹起来,少用匿名函数,以便可以重复的多次使用,这也是可以便于程序流程的理解。
除了常见的一种回调函数作为异步处理,还有promises,Generators,async是处理异步处理的方式,,关于这三个我也在学习当中,理论的东西虽是概念,没有大量代码的编写,个人觉得是很难理解这些东西,但是代码就是这些语言文字实实在在的转化,骚年们,加油,加油....
原文阅读出处:http://callbackhell.com/
作者:川川,一个靠前排的90后帅小伙,争做一个具有情怀的代码男,愿做你耳朵旁边的暖男,眼睛笔尖下的窗户