异步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
现在我们的应用程序只有两行代码,并具有以下优点:
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,它会向你显示你的代码中的每一个回调,并带有未处理的错误
不要嵌套功能。给他们姓名并将他们放在程序的顶层
避免回调地狱的最重要的方面是将功能移开,以便程序流程可以更容易理解,而无需新手参与功能的所有细节以了解程序正在尝试做什么
你可以先将函数移动到文件底部,然后使用require(’./ photo-helpers.js’)等相关需求将它们移动到另一个文件中,然后将它们移动到独立模块like require(‘image-resize’))
以下是创建模块时的一些经验法则:
回调地狱最主要的就是因为功能逻辑代码嵌套的层次太多,导致可读性降低,维护困难,避免回调地狱的最重要的方面是将功能移开,保持代码简单,不嵌套并分成小模块,也就是多多进行代码封装,将你所要的属性和方法用function关键字包裹起来,而且还要给它取一个有意义的名字,例如:页面上弹框,显示,隐藏,下拉等各个功能小模块,分别用有名函数给包裹起来,少用匿名函数,以便可以重复的多次使用,这也是可以便于程序流程的理解
除了常见的一种回调函数作为异步处理,还有promises,Generators,async是处理异步处理的方式,关于这三个我也在学习当中,理论的东西虽是概念,没有大量代码的编写,个人觉得是很难理解这些东西,但是代码就是这些语言文字实实在在的转化,骚年们,加油,加油…
原文出处:https://www.jianshu.com/p/39adf6ab8ad1