JavaScript 中回调地狱的今生前世
1. 讲个笑话
JavaScript 是一门编程语言
2. 异步编程
JavaScript 由于某种原因是被设计为单线程的,同时由于 JavaScript 在设计之初是用于浏览器的 GUI 编程,这也就需要线程不能进行阻塞。
所以在后续的发展过程中基本都采用异步非阻塞的编程模式。
简单来说,异步编程就是在执行一个指令之后不是马上得到结果,而是继续执行后面的指令,等到特定的事件触发后,才得到结果。
也正是因为这样,我们常常会说: JavaScript 是由事件驱动的。
3. 异步实现
用 JavaScript 构建一个应用的时候经常会遇到异步编程,不管是 Node 服务端还是 Web 前端。
那如何去进行异步编程呢?就目前的标准以及草案来看,主要有下面的几种方式:
回调
promise
Generator
await/async
3.1 回调
这种异步的方式是最基础的实现,如果你曾经写过一点的 Node, 可能经常会遇到这样的代码:
connection.query(sql, (err, result) => {
if(err) {
console.err(err)
} else {
connection.query(sql, (err, result) => {
if(err) {
console.err(err)
} else {
...
}
})
}
})
如此,connection.query() 是一个异步的操作,我们在调用他的时候,不会马上得到结果,而是会继续执行后面的代码。这样,如果我们需要在查到结果之后才做某些事情的话,就需要把相关的代码写在回调里面,如果涉及到多个这样的异步操作,就势必会陷入到回调地狱中去。
这种回调地狱不仅看起来很不舒服,可读性比较差;除此之外还有比较重要的一点就是对异常的捕获无法支持。
3.2 Promise
Promise 是 ES 2015 原生支持的,他把原来嵌套的回调改为了级联的方式。
一般着,我们对一个 Promise 可以这样写:
var a = new Promise(function(resolve, reject) {
setTimeout(function() {
resolve('1')
}, 2000)
})
a.then(function(val) {
console.log(val)
})
如果要涉及到多个异步操作的顺序执行问题,我们可以这样写:
var a = new Promise(function(resolve, reject) {
setTimeout(function() {
resolve('1')
}, 2000)
})
a
.then(function(val){
console.log(val)
return new Promise(function(resolve, reject) {
setTimeout(function() {
resolve('2')
}, 2000)
})
})
.then(function(val) {
console.log(val)
})
也可以把函数抽离出来
var a = new Promise(function(resolve, reject) {
setTimeout(function() {
resolve('1')
}, 2000)
})
function b(val) {
console.log(val)
return new Promise(function(resolve, reject) {
setTimeout(function() {
resolve('2')
}, 2000)
})
}
a.then(b).then(function(val) {
console.log(val)
})
我们只需要 return 一个 Promise 即可实现这种多个异步操作的顺序执行。
粗略来看,这是一个比较优雅的异步解决方案了,并且在 Promise 中我们也可以实现分级的 catch。
但对于之前接触过其他语言的同学来说还是比较别扭的。那能否用同步的方式来书写异步呢?
3.3 Generator
在 ES 2015 中,出现了 Generator 的语法,熟悉 Python 的同学肯定对这种语法有点了解。
简单来说,Generator 可以理解为一个可以遍历的状态机,调用 next 就可以切换到下一个状态。
在 JavaScript 中,Generator 的 function 与 函数名之间有一个 *, 函数内部使用 yield 关键词,定义不同的状态。
先看一段代码:
function a() {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(1)
}, 2000)
});
};
var b = co(function *() {
var val = yield a();
console.log(val)
})
b()
上面的这段代码是借助 TJ 的 co 实现的,依照约定,co 中 yield 后面只能跟 Thunk 或者 Promise.
co 的实现代码很短,简单来说大体是这样:
// http://www.alloyteam.com/2015/04/solve-callback-hell-with-generator/
function co(genFun) {
// 通过调用生成器函数得到一个生成器
var gen = genFun();
return function(fn) {
next();
function next(err, res) {
if(err) return fn(err);
// 将res传给next,作为上一个yield的返回值
var ret = gen.next(res);
// 如果函数还没迭代玩,就继续迭代
if(!ret.done) return ret.value(next);
// 返回函数最后的值
fn && fn(null, res);
}
}
}
简单来说就是一直借助 generator 的 next 进行迭代,直到完成这个异步操作才返回。当前人家官方的 co 是 200 行代码,支持异步操作的并行:
co(function *() {
var val = yield [
yield asyn1(),
yield asyn2()
]
})()
但如果我们使用 co,强迫症们就会觉得这不是标准的写法,有点 hack 小子的感觉。
幸运的是,在 ES 2016 的草案中,终于提出了标准的写法。
3.4 await/async
这是在 ES 2016 中引入的新关键词,这将在语言层面彻底解决 JavaScript 的异步回调问题,目前可以借助 babel 在生产环境中使用。使用 await/async 可以让异步的操作以同步的方式来写。
使用方法和 co 非常类似,同时也支持同步写法的异常捕获。
function a() {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(1)
}, 2000)
})
}
var b = async function() {
var val = await a()
console.log(val)
}
b()
如果上述的代码完全用 Promise 实现,极有可能是下面的代码:
function a() {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(1);
}, 2000);
});
};
var b = function() {
a().then(val) {
console.log(val)
}
console.log(val)
};
b();
相比较来说,await/async 解决了完全使用 Promise 的一个极大痛点——不同Promise之间共享数据问题:
Promise 需要设定外层数据开始共享,这样就需要在每个then里面进行赋值,而 await/async 就不存在这样的问题,只需要以同步的方式去写就可以了。
await/async 对异常的支持也是特别好的:
function a() {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(1)
}, 2000)
});
};
var b = async function() {
try {
var val = await a()
console.log(val)
} catch (err) {
console.log(err)
}
};
b();
作者:rccoder
原文:https://github.com/rccoder/blog/issues/17
JavaScript 中回调地狱的今生前世的更多相关文章
- 避免Node.js中回调地狱
为了解决这个阻塞问题,JavaScript严重依赖于回调,这是在长时间运行的进程(IO,定时器等)完成后运行的函数,因此允许代码执行经过长时间运行的任务. downloadFile('example. ...
- JavaScript中回调函数的使用
在JavaScript中,回调函数具体的定义为:函数A作为参数(函数引用)传递到另一个函数B中,并且这个函数B执行函数A.我们就说函数A叫做回调函数.如果没有名称(函数表达式),就叫做匿名回调函数. ...
- Javascript中回调函数的学习笔记
function a_b(kkis){ document.body.style.background ='red'; kkis(); } function fli(){ alert('######## ...
- [转]理解与使用Javascript中的回调函数
在Javascript中,函数是第一类对象,这意味着函数可以像对象一样按照第一类管理被使用.既然函数实际上是对象:它们能被“存储”在变量中,能作为函数参数被传递,能在函数中被创建,能从函数中返回. 因 ...
- 【JavaScript】理解与使用Javascript中的回调函数
在Javascript中,函数是第一类对象,这意味着函数可以像对象一样按照第一类管理被使用.既然函数实际上是对象:它们能被“存储”在变量中,能作为函数参数被传递,能在函数中被创建,能从函数中返回. 因 ...
- 理解与使用Javascript中的回调函数
在Javascript中,函数是第一类对象,这意味着函数可以像对象一样按照第一类管理被使用.既然函数实际上是对象:它们能被“存储”在变量中,能作为函数参数被传递,能在函数中被创建,能从函数中返回. 因 ...
- 理解与使用Javascript中的回调函数 -2
在javascript中回调函数非常重要,它们几乎无处不在.像其他更加传统的编程语言都有回调函数概念,但是非常奇怪的是,完完整整谈论回调函数的在线教程比较少,倒是有一堆关于call()和apply() ...
- JavaScript中的回调地狱及解决方法
1.回调地狱 在使用JavaScript时,为了实现某些逻辑经常会写出层层嵌套的回调函数,如果嵌套过多,会极大影响代码可读性和逻辑,这种情况也被成为回调地狱.比如说你要把一个函数 A 作为回调函数,但 ...
- 理解和使用 JavaScript 中的回调函数
理解和使用 JavaScript 中的回调函数 标签: 回调函数指针js 2014-11-25 01:20 11506人阅读 评论(4) 收藏 举报 分类: JavaScript(4) 目录( ...
随机推荐
- [转]MySQL忘记密码的正确解决方法
http://database.51cto.com/art/201005/201986.htm 以下的文章主要介绍的是MySQL忘记密码的正确解决方法,在实际操作中如果你忘记MySQL密码是一件很头痛 ...
- awk常见用法
awk作为linux字符搜索,结果统计的实用工具,其在linux日常运维中有着很多的巧妙运用.下面就来技术一下刚刚学到的技巧 #awk命令统计文件夹下所有文件大小 ls -l |awk 'BEGIN ...
- 分布式系统的CAP理论
一.CAP理论概述 一个分布式系统最多只能同时满足一致性(Consistency).可用性(Availability)和分区容错性(Partition tolerance)这三项中的两项. 二.CAP ...
- mysql数据库授权
mysql数据库授权所有人 grant all privileges on *.* to 'root'@'%' identified by 'password'; flush privileges; ...
- BZOJ4556 [Tjoi2016&Heoi2016]字符串 SA ST表 二分答案 主席树
原文链接https://www.cnblogs.com/zhouzhendong/p/BZOJ4556.html 题目传送门 - BZOJ4556 题意 给定一个长度为 $n$ 的字符串 $s$ . ...
- AtCoder Grand Contest 026 (AGC026) E - Synchronized Subsequence 贪心 动态规划
原文链接https://www.cnblogs.com/zhouzhendong/p/AGC026E.html 题目传送门 - AGC026E 题意 给定一个长度为 $2n$ 的字符串,包含 $n$ ...
- 067 HA与updateStateByKey结合
是HA与updateStateByKey相结合的程序. 有点问题,有点奇怪,重启项目后运行没有问题,但是第三次启动的时候,就不会在打印数据了,有点问题. 1.程序 package com.stream ...
- day70 cookie & session 前后端交互分页显示
本文转载自qimi博客,cnblog.liwenzhou.com 概要: 我们的cookie是保存在浏览器中的键值对 为什么要有cookie? 我们在访问浏览器的时候,千万个人访问同一个页面,我们只要 ...
- Ubuntu16.04多个版本GCC编译器的安装和切换
这几天在配置交叉编译ARM开发板的linux内核的过程中碰到了很多问题,其中包括了GCC版本太高等问题,由此我打算安装其他老的版本给我的Ubuntu16.04.实验过程如下: 官网的教程是最牛逼的,先 ...
- network is unreachable 网关PING不通解决办法
所里有几台机器没有办法ping通网关,但是ping交换机里的其它机器都可以ping通,其它机器ping网关也可以ping通.那么就排出了硬件的故障,主要问题就在问题机器的路由表上了. 看一下路由表 r ...