异步Javascript代码,或者说使用callback的Javascript代码,很难符合咱们的直观理解。不少代码最终会写成这样:node
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跟他们是不同的。git
某种使用JavaScript函数的惯例用法的名字叫作回调。JavaScript语言中没有一个叫“回调”的东西,它仅仅是一个惯例用法的名字。大多数函数会马上返回执行结果,使用回调的函数一般会通过一段时间后才输出结果。名词“异步”,简称“async”,只是意味着“这将花费一点时间”或者说“在未来某个时间发生而不是如今”。一般回调只使用在I/O操做中,例以下载文件,读取文件,链接数据库等等。github
当你调用一个正常的函数时,你能够向下面的代码那样使用它的返回值:数据库
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
函数,当下载结束,回调会被调用。若是下载成功,传入photo
给回调;下载失败,传入error
给回调。async
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')
人们理解回调的最大障碍在于理解一个程序的执行顺序。在上面的例子中,发生了三件事情。ide
handlePhoto
函数downloadPhoto
函数被调用而且传入了handlePhoto
最为它的回调请你们注意,起初handlePhoto
函数仅仅是被建立并被做为回调传递给了downloadPhoto
,它尚未被调用。它会等待downloadPhoto
函数完成了它的任务才会执行。这可能须要很长一段时间(取决于网速的快慢)。模块化
这个例子意在阐明两个重要的概念:函数
handlePhoto
回调只是一个存放未来进行的操做的方式糟糕的编码习惯形成了回调地狱。幸运的是,编写优雅的代码不是那么难!
你只须要遵循三大原则:
下面是一堆乱糟糟的代码,使用browser-request作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 }
请你们注意,函数声明在程序的底部,可是咱们在函数声明以前就能够调用它。这是函数提高的做用。
任何人都有有能力建立模块,这点很是重要。Isaac Schlueter(NodeJS项目成员)说过“写出一些小模块,每一个模块只作一件事情,而后把他们组合起来放入其余的模块作一个复杂的事情。只要你不想陷入回调地狱,你就不会落入那般田地。”
。
让咱们把上面的例子修改一下,改成一个模块。
下面是一个名为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模块化的用法。如今已经有了 formuploader.js
文件,咱们只须要引入它并使用它。请看下面的代码:
var formUploader = require('formuploader') document.querySelector('form').onsubmit = formUploader.submit
咱们的应用只有两行代码而且还有如下好处:
formuploader
函数的所有代码formuploader
能够在其余地方复用有三种不一样类型的异常:语法异常,运行时异常和平台异常。语法异常一般由开发人员在第一次解释代码时捕获,运行时异常一般在代码运行过程当中由于bug触发,平台异常一般因为没有文件的权限,硬盘错误,无网络连接等问题形成。这一部分主要来处理最后一种异常:平台异常。
前两个大原则意在提升代码可读性,可是第三个原则意在提升代码的稳定性。在你与回调打交道的时候,你一般要处理发送请求,等待返回或者放弃请求等任务。任何有经验的开发人员都会告诉你,你历来不知道哪里回出现问题。因此你有必要提早准备好,异常老是会发生。
把回调函数的第一个参数设置为error对象,是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
对象是一个约定俗成的惯例,提醒你记得去处理异常。若是它是第二个参数,你更容易把它忽略掉。