产生原因

在前端技术刀耕火种时代,让人闻之变色的一个词就是“回调”。因为设计原因导致 JavaScript 这门语言是单线程执行的,这就导致一些耗时的操作会阻塞当前运行线程。

为了解决这个问题,机智的开发者们引入了“同步”和“异步”这两个概念。

打个很简单的比方,去肯德基买汉堡,在柜台上排队买就是同步的,因为必须等待前面的人买好取到餐才能轮到我们;而使用手机点餐就是异步的,下完单之后我们可以想干嘛干嘛,等到收银员喊到我的号的时候再去取餐即可。

在以上场景中,去取餐就是我向肯德基注册的一个回调函数,当我的餐准备就绪,收银员喊我,就相当于调用了回调函数。

但是为什么程序员们谈回调色变呢?究其原因是因为层层回调会造成所谓的“回调地狱(callback hell)”

就像这样:

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))

}

})

})

}

})

好吧,不管我们能不能理解以上代码。总之,当多个异步任务需要顺序执行的时候,在刀耕火种的年代,程序员们不得不忍受着这样的煎熬。

解决办法

其实解决回调地狱的办法有很多,从代码书写层面就可以将绝大部分回调代码写的尽量简单易懂。但这都不是我们今天的主角,我们今天主要讲讲 Promise。

Promise

Promise 自 ES6 起成为 JavaScript 的语言标准。但是其最早是由 JavaScript 社区提出并实现的。Promise 规范和标准了异步操作 API,基本上所有的异步操作都可以使用 Promise 的写法处理。Promise 对象内部保存着异步操作的结果,并通过链式调用的方式避免了回调函数层层嵌套的写法。

基本用法

new Promise((resolve, reject) => {

setTimeout(() => {

resolve('success')

}, 1000)

}).then(res => {

console.log(res) // success

})

Promise 构造函数接收一个函数作为参数,这个函数的两个参数分别为 resolve 和 reject。这也是两个函数,其值会由 JavaScript 传入,使用者只需要在异步操作完成时调用 resolve 函数并传入下一步操作所需要的值即可。使用者可以通过链式调用的方式为 Promise 对象添加后续操作。

reject 函数则是在异步操作发生异常时被调用,此时 Promise 可以捕获到传入 reject 参数中的值。

new Promise((resolve, reject) => {

setTimeout(() => {

reject('error')

}, 1000)

})

.then(res => console.log('进入then: ' + res))

.catch((e) => console.log('进入catch: ' + e)) // 进入catch: error

值得一提的是,Promise 代码不同于其他函数,对传入 Promise 构造方法中的函数不需要显示的调用执行,其会直接执行,且是作为同步任务被执行的。

setTimeout(() => console.log('timeout'))

new Promise((resolve, reject) => {

console.log('Promise')

});

console.log('main')

/*

* Promise

* main

* timeout

*/

改写回调函数

在远古时期,使用 jQuery 发送 ajax 请求的代码类似于以下:

$.ajax({

url: '/api/user/getInfo',

data: {},

dataType: 'json',

success: function (data) {

// process success

},

error: function(err) {

// process error

}

})

如果此时需要有操作在 ajax 请求之后执行,则就需要在 success 上挂载回调函数。如果此时这个操作内又包含了异步操作,那代码就会变得冗长乏味,像老太太的裹脚布一般。

而在有了 Promise 之后,我们可以将普通的 ajax 方法封装为 Promise 方法

function ajax(url, data = {}) {

return new Promise((resolve, reject) => {

$.ajax({

url,

data,

dataType: 'json',

success: resolve

error: reject

})

})

}

注意 ajax 函数,函数构造了 Promise 对象并将其 return 出来,这是帮助我们书写可读性高的异步代码的关键。

之后,我们可以使用 Promise 的链式调用方式来处理请求。

ajax('/api/user/getInfo')

.then(result => {

// process result

return ajax('/api/user/getOrder', { id: result.userId }))

}).then(result => {

// process result

return ajax('/api/user/getMessage', { id: result.userId })

}).then(result => {

// process result

// do something ...

})

注意每一个 then 的参数函数内我们又调用了个 ajax 函数,即返回了一个 Promise 对象,这也是 Promise 的链式调用的关键所在。

缺陷

Promise 函数改变了之前回调地狱的写法,但是在根本上还是函数套函数,看起来不是那么的美观

Promise 一经执行,无法中断,除非抛出异常

在 Promise 外部无法通过 try/catch 的方式捕获 Promise 内部抛出的异常。

Async/Await

可以延展的说一下 async/await。尽管这是一个 ES7 标准内的语法。

async/await 可以将 Promise 代码组织的更像同步代码一样,其书写方式就和之前写同步代码一样,只是需要加上相应关键字。

例如,将之前的 Promise 代码改写为 async/await

async function request(id) {

const result1 = await ajax('/api/user/getInfo', { id })

// process result1

const result2 = await ajax('/api/user/getOrder', { id })

// process result2

const result3 = await ajax('/api/user/getMessage', { id })

// process result3

}

request(1)

必须记住的是在函数上添加 async 关键字,从而可以在函数内使用 await,否则的话会报错。

尽管 async/await 的书写方式很像同步代码,但是这和同步代码是不同的。

打个比方,执行一段很耗时的操作,同步的方式时 JS 会想,我就在这等着你,你这个操作做完了我才能去做别的事。使用 async/await 时 JS 会想,反正闲着也是闲着,我可以先把手头上的工作(主执行栈)停一停,看看有没有其他事情(回调队列或者其它执行栈)可以做的。

const asyncFunc = (n) => new Promise(res => setTimeout(() => res(n), 5000))

const call = async (n) => {

const result = await asyncFunc(n)

console.log(result)

}

setTimeout(() => {

console.log('event call!!')

}, 2000)

call(50)

// event call!!

// 50

那么这里可以引入一个问题。

小张同学在看完这篇文章之后,希望使用 async/await 改写计算斐波那契数列的函数,从而达到在程序计算时也可以执行其他执行栈的函数。小张同学的代码如下,你知道他错在哪里了吗?

/**

* 小张希望的输出是:

* event call!!

* fib(50)的值

* 但是运行时却不是这样的,而且程序还会卡死

* 不是说async/await可以将函数变为异步吗?那执行结果会与预期不一致呢?

*/

const fib = (n) => n === 0 ? 1 : n === 1 ? 1 : fib(n - 1) + fib(n - 2)

const asyncFunc = (n) => new Promise(res => res(fib(n)))

const call = async (n) => {

const result = await asyncFunc(n)

console.log(result)

}

setTimeout(() => {

console.log('event call!!')

}, 2000)

call(50)

展望未来

或许管道流式操作可能成为异步处理方式的新宠?

推荐阅读

使用promise解决回调地狱_Promise 技术调研 - 回调地狱的产生原因与解决方式相关推荐

  1. 计算机无法启动的原因及解决方法,电脑软件无法启动常见的三种原因以及解决方法...

    日常生活中,我们的电脑总会遇到各种各样的问题,掌握了修电脑的技能,会对我们有很大的帮助,至少出现问题的时候不会手忙脚乱,打乱我们的工作计划,或者花冤枉钱拿出去修,下面就给大家简单讲述电脑软件无法启动常 ...

  2. 计算机死机的解决方法及操作步骤,电脑出现死机的原因及解决方法

    很多人再使用电脑的过程中,都有遇到过电脑无缘无故出现死机的情况,导致无法正常使用电脑,这是怎么回事呢?今天U大侠小编就和大家说说电脑死机的原因及解决方法吧! 1.CPU内部CACHE(缓存)(现象:电 ...

  3. 微信登录失败——授权回调域名校验出错,错误码:10003原因及解决办法

    配置文件这个路径是对应的微信端页面和JS相关的域名:不是后台的域名,对于前后端完全分离有不同域名的,要分清了: 微信授权回调页面域名和JAVA后台配置文件中的域名不一致,会导致微信登录失败--授权回调 ...

  4. 计算机主机信号不过去的解决办法,电脑显示器无信号黑屏是什么原因|怎么解决【图解】-太平洋IT百科...

    电脑显示器无信号怎么办 经常有朋友会遇到显示器无信号的情况,相比显示器没有任何显示而言,显示器无信号电脑故障其实更容易解决.为什么这么说呢?其实原因很简单,如果显示器没任何显示,人们很难一下子判断是显 ...

  5. 彻底解决TortoiseGit中.gitignore文件失效,忽略文件失效原因及解决办法

    我更新了我的 .gitignore 文件,现在我想更新索引. 我想执行 git rm --cached 命令.但是我该怎么做TortoiseGit? 答案是: 右键单击该文件,选择TortoiseGi ...

  6. 张驰咨询:质量人如何运用六西格玛培训解决难以突破的技术问题

    六西格玛是一种基于数据和统计的方法,旨在改进业务流程,提高质量和效率.作为质量人员,您可以使用这种方法来解决难以突破的技术问题,以下是六西格玛培训解决技术问题的步骤: 1.确定问题范围和定义问题 在使 ...

  7. python内核死亡的原因_Kernel Panic常见原因以及解决方法

    Technorati 标签: Kernel Panic 出现原因 1. Linux在中断处理程序中,它不处于任何一个进程上下文,如果使用可能睡眠的函数,则系统调度会被破坏,导致kernel panic ...

  8. Java读带有BOM的UTF-8文件乱码原因及解决方法

    Java读带有BOM的UTF-8文件乱码原因及解决方法 Java读带有BOM的UTF-8文件乱码原因及解决方法 - daimojingdeyu - ITeye技术网站 Java读带有BOM的UTF-8 ...

  9. 资源不足的情况怎么设置sparkrdd并行度_监控录像机资源不足或达到上限的原因及解决方法!...

    在安装网络监控摄像机过程中,很多人遇到硬盘录像机画面上提升"资源不足"或性能"达到上限"的问题,新手遇到这样的问题会选择重启录像机,但是几次反复发现并不能解决. ...

  10. Java编码问题原因以及解决

    Java编码问题原因以及解决 参考文章: (1)Java编码问题原因以及解决 (2)https://www.cnblogs.com/caoshouling/p/8644596.html 备忘一下.

最新文章

  1. 我们单位里新来的应届毕业生(转贴)
  2. mysql修改数据库字符集,编码
  3. Visual Studio 2005 Service Pack 1 正式版发布
  4. 『数据库』数据库笔记
  5. 证明创建runnable实例和普通类时间一样长
  6. 建行优盾制单重要还是复核重要_想秒批建行5万+白金信用卡,你得满足这些条件!...
  7. SCALA环境搭建(2)_scala源文件编写和运行---大数据之_SCALA工作笔记005
  8. 为什么新一代的Rust、Go等编程语言都如此讨厌if-else、Switch结构
  9. 图书信息管理系统报告linux,C语言图书管理系统 带程序报告
  10. hi3519多sensor设置说明
  11. 计算机主机mac地址怎么查,怎么查看电脑的Mac地址
  12. 洛谷P1427 小鱼的数字游戏
  13. 明源软件诚聘.NET软件开发工程师
  14. java雷霆战机项目收获_java实习项目_雷霆战机
  15. RabbitMQ连接超时问题
  16. 天问: 量子力学中的波函数到底表示什么?
  17. コナン純黒のナイトメア20180715
  18. 35岁没有技能转行做什么工作岗位好?
  19. 常用的16个Java实用工具类,Java开发人员请收藏!
  20. BCB6.0 Import Type Library Excel2003时报错:因为我安装了Excel2007兼容包 的缘故

热门文章

  1. word文档通配符换行_Word怎么批量删除分隔符
  2. php随机一句话,PHP简单实现一言 / 随机一句功能
  3. 15000cd是多少流明_光通量(lm)发光强度(cd)照度单位(lux)之间的关系
  4. Windows使用MinGW编译ffmpeg
  5. 中国互联网杀毒软件的简要发展历史
  6. 九宫格拼图 | 8Puzzle | C/C++实现
  7. Vagrant的各种坑介绍
  8. python代写学生作业_data留学生作业代做、代写Python程序设计作业、代做Python实验作业...
  9. Feb23 小白《linux就该这么学》学习笔记5
  10. 初次 使用RuoYi 若依框架总结(前端)