翼度科技»论坛 编程开发 JavaScript 查看内容

记录--没有await,如何处理“回调地狱”

4

主题

4

帖子

12

积分

新手上路

Rank: 1

积分
12
这里给大家分享我在网上总结出来的一些知识,希望对大家有所帮助



太长不看


  • 不要嵌套使用函数。给每个函数命名并把他们放在你代码的顶层
  • 利用函数提升。先使用后声明。
  • 处理每一个异常
  • 编写可以复用的函数,并把他们封装成一个模块
什么是“回调地狱”?

异步Javascript代码,或者说使用callback的Javascript代码,很难符合我们的直观理解。很多代码最终会写成这样:
  1. fs.readdir(source, function (err, files) {
  2.   if (err) {
  3.     console.log('Error finding files: ' + err)
  4.   } else {
  5.     files.forEach(function (filename, fileIndex) {
  6.       console.log(filename)
  7.       gm(source + filename).size(function (err, values) {
  8.         if (err) {
  9.           console.log('Error identifying file size: ' + err)
  10.         } else {
  11.           console.log(filename + ' : ' + values)
  12.           aspect = (values.width / values.height)
  13.           widths.forEach(function (width, widthIndex) {
  14.             height = Math.round(width / aspect)
  15.             console.log('resizing ' + filename + 'to ' + height + 'x' + height)
  16.             this.resize(width, height).write(dest + 'w' + width + '_' + filename, function(err) {
  17.               if (err) console.log('Error writing file: ' + err)
  18.             })
  19.           }.bind(this))
  20.         }
  21.       })
  22.     })
  23.   }
  24. })
复制代码
看到上面金字塔形状的代码和那些末尾参差不齐的 }) 了吗?这就是广为人知的回调地狱了。
人们在编写JavaScript代码时,误认为代码是按照我们看到的代码顺序从上到下执行的,这就是造成回调地狱的原因。在其他语言中,例如C,Ruby或者Python,第一行代码执行结束后,才会开始执行第二行代码,按照这种模式一直到执行到当前文件中最后一行代码。随着你学习深入,你会发现JavaScript跟他们是不一样的。
什么是回调(callback)?

某种使用JavaScript函数的惯例用法的名字叫做回调。JavaScript语言中没有一个叫“回调”的东西,它仅仅是一个惯例用法的名字。大多数函数会立刻返回执行结果,使用回调的函数通常会经过一段时间后才输出结果。名词“异步”,简称“async”,只是意味着“这将花费一点时间”或者说“在将来某个时间发生而不是现在”。通常回调只使用在I/O操作中,例如下载文件,读取文件,连接数据库等等。
当你调用一个正常的函数时,你可以向下面的代码那样使用它的返回值:
  1. var result = multiplyTwoNumbers(5, 10)
  2. console.log(result)
  3. // 50 gets printed out
复制代码
然而使用回调的异步函数不会立刻返回任何结果。
  1. var photo = downloadPhoto('http://coolcats.com/cat.gif')
  2. // photo is 'undefined'!
复制代码
在这种情况下,上面那张gif图片可能需要很长的时间才能下载完成,但你不想你的程序在等待下载完成的过程中中止(也叫阻塞)。
于是你把需要下载完成后运行的代码存放到一个函数中(等待下载完成后再运行它)。这就是回调!你把回调传递给downloadPhoto函数,当下载结束,回调会被调用。如果下载成功,传入photo给回调;下载失败,传入error给回调。
  1. downloadPhoto('http://coolcats.com/cat.gif', handlePhoto)
  2. function handlePhoto (error, photo) {
  3.   if (error) console.error('Download error!', error)
  4.   else console.log('Download finished', photo)
  5. }
  6. console.log('Download started')
复制代码
人们理解回调的最大障碍在于理解一个程序的执行顺序。在上面的例子中,发生了三件事情。

  • 声明handlePhoto函数
  • downloadPhoto函数被调用并且传入了handlePhoto最为它的回调
  • 打印出Download started
请大家注意,起初handlePhoto函数仅仅是被创建并被作为回调传递给了downloadPhoto,它还没有被调用。它会等待downloadPhoto函数完成了它的任务才会执行。这可能需要很长一段时间(取决于网速的快慢)。
这个例子意在阐明两个重要的概念:

  • handlePhoto回调只是一个存放将来进行的操作的方式
  • 事情发生的顺序并不是直观上看到的从上到下,它会当某些事情完成后再跳回来执行。
怎样解决“回调地狱”问题?

糟糕的编码习惯造成了回调地狱。幸运的是,编写优雅的代码不是那么难!
你只需要遵循三大原则
1. 减少嵌套层数(Keep your code shallow)

下面是一堆乱糟糟的代码,使用browser-request做AJAX请求。
  1. var form = document.querySelector('form')
  2. form.onsubmit = function (submitEvent) {
  3.   var name = document.querySelector('input').value
  4.   request({
  5.     uri: "http://example.com/upload",
  6.     body: name,
  7.     method: "POST"
  8.   }, function (err, response, body) {
  9.     var statusMessage = document.querySelector('.status')
  10.     if (err) return statusMessage.value = err
  11.     statusMessage.value = body
  12.   })
  13. }
复制代码
这段代码包含两个匿名函数,我们来给他们命名。
  1. var form = document.querySelector('form')
  2. form.onsubmit = function formSubmit (submitEvent) {
  3.   var name = document.querySelector('input').value
  4.   request({
  5.     uri: "http://example.com/upload",
  6.     body: name,
  7.     method: "POST"
  8.   }, function postResponse (err, response, body) {
  9.     var statusMessage = document.querySelector('.status')
  10.     if (err) return statusMessage.value = err
  11.     statusMessage.value = body
  12.   })
  13. }
复制代码
如你所见,给匿名函数一个名字是多么简单,而且好处立竿见影:

  • 起一个一望便知其函数功能的名字让代码更易读
  • 当抛出异常时,你可以在stacktrace里看到实际出异常的函数名字,而不是"anonymous"
  • 允许你合理安排函数的位置,并通过函数名字调用它
现在我们可以把这些函数放在我们程序的顶层。
  1. document.querySelector('form').onsubmit = formSubmit
  2. function formSubmit (submitEvent) {
  3.   var name = document.querySelector('input').value
  4.   request({
  5.     uri: "http://example.com/upload",
  6.     body: name,
  7.     method: "POST"
  8.   }, postResponse)
  9. }
  10. function postResponse (err, response, body) {
  11.   var statusMessage = document.querySelector('.status')
  12.   if (err) return statusMessage.value = err
  13.   statusMessage.value = body
  14. }
复制代码
请大家注意,函数声明在程序的底部,但是我们在函数声明之前就可以调用它。这是函数提升的作用。
2.模块化(Modularize)

任何人都有有能力创建模块,这点非常重要。写出一些小模块,每个模块只做一件事情,然后把他们组合起来放入其他的模块做一个复杂的事情。只要你不想陷入回调地狱,你就不会。让我们把上面的例子修改一下,改为一个模块。
下面是一个名为formuploader.js的新文件,包含了我们之前使用过的两个函数。
  1. module.exports.submit = formSubmit
  2. function formSubmit (submitEvent) {
  3.   var name = document.querySelector('input').value
  4.   request({
  5.     uri: "http://example.com/upload",
  6.     body: name,
  7.     method: "POST"
  8.   }, postResponse)
  9. }
  10. function postResponse (err, response, body) {
  11.   var statusMessage = document.querySelector('.status')
  12.   if (err) return statusMessage.value = err
  13.   statusMessage.value = body
  14. }
复制代码
module.exports是node.js模块化的用法。现在已经有了 formuploader.js 文件,我们只需要引入它并使用它。请看下面的代码:
  1. var formUploader = require('formuploader')
  2. document.querySelector('form').onsubmit = formUploader.submit
复制代码
我们的应用只有两行代码并且还有以下好处:

  • 方便新开发人员理解你的代码 -- 他们不需要费尽力气读完formuploader函数的全部代码
  • formuploader可以在其他地方复用
3.处理每一个异常(Handle every single error)

有三种不同类型的异常:语法异常,运行时异常和平台异常。语法异常通常由开发人员在第一次解释代码时捕获,运行时异常通常在代码运行过程中因为bug触发,平台异常通常由于没有文件的权限,硬盘错误,无网络链接等问题造成。这一部分主要来处理最后一种异常:平台异常。
前两个大原则意在提高代码可读性,但是第三个原则意在提高代码的稳定性。在你与回调打交道的时候,你通常要处理发送请求,等待返回或者放弃请求等任务。任何有经验的开发人员都会告诉你,你从来不知道哪里回出现问题。所以你有必要提前准备好,异常总是会发生。
把回调函数的第一个参数设置为error对象,是Node.js中处理异常最流行的方式。
  1. var fs = require('fs')
  2. fs.readFile('/Does/not/exist', handleFile)
  3. function handleFile (error, file) {
  4.    if (error) return console.error('Uhoh, there was an error', error)
  5.    // otherwise, continue on and use `file` in your code
  6. }
复制代码
把第一个参数设为error对象是一个约定俗成的惯例,提醒你记得去处理异常。如果它是第二个参数,你更容易把它忽略掉。
本文转载于:

https://juejin.cn/post/7294166986195533843

如果对您有所帮助,欢迎您点个关注,我会定时更新技术文档,大家一起讨论学习,一起进步。

 


来源:https://www.cnblogs.com/smileZAZ/p/17912003.html
免责声明:由于采集信息均来自互联网,如果侵犯了您的权益,请联系我们【E-Mail:cb@itdo.tech】 我们会及时删除侵权内容,谢谢合作!

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x

举报 回复 使用道具