前言
在 MDN 的 JavaScript 系列中我们已经学习了 callback、promise、generator、async/await。而在这一篇文章中,作者将以实际样例阐述异步发展历史,介绍每种实现方式的优势与不足,以期帮助读者熟悉历史进程并把握异步发展的脉络。
异步
几十年前的导航网站,清爽又简单,没有什么特别的功能,只是单纯的展示,现成的网页在服务器上静静躺着,高效毫无压力,让人很喜欢。
几十年后的今天,静态页面远不能满足用户的需求,网站变得复杂起来,用户交互越来越频繁,从而产生大量复杂的内部交互,为了解决这种复杂,出现了各种系统“模式”,从而很容易的在外部获取数据,并实时展示给用户。
获取外部数据实际上就是“网络调用”,这个时候“异步”这个词汇出现了。
异步 callbacks
场景
let readFile = (path, callBack) => {
setTimeout(function () {
callBack(path)
}, 1000)
}
readFile('first', function () {
console.log('first readFile success')
readFile('second', function () {
console.log('second readFile success')
readFile('third', function () {
console.log('third readFile success')
readFile('fourth', function () {
console.log('fourth readFile success')
readFile('fifth', function () {
console.log('fifth readFile success')
})
})
})
})
})
优点:
- 解决了同步问题(即解决了一个任务时间长时,后面的任务排队,耗时太久,使程序的执行变慢问题)
缺点:
- 回调地狱(多层级嵌套),会导致逻辑混乱,耦合性高,改动一处就会导致全部变动,嵌套多时,错误处理复杂
- 不能使用 try...catch 来抓取错误
- 不能 return
- 可读性差
Promise
场景
let readFile = (path) => {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (!path) {
reject('error!!!')
} else {
console.log(path + ' readFile success')
resolve()
}
}, 1000)
})
}
readFile('first')
.then(() => readFile('second'))
.then(() => readFile('third'))
.then(() => readFile('fourth'))
.then(() => readFile('fifth'))
优点:
- 状态改变后,就不会再变,任何时候都可以得到这个结果
- 可以将异步操作以同步操作的流程表达出来,避免了层层嵌套的回调函数
- 一定程度上解决了回调地狱的可读性问题
缺点:
- 无法取消 promise
- 当处于 pending 状态时,无法得知目前进展到哪一个阶段
- 代码冗余,有一堆任务时也会使语义不清晰
Generator
特征
- function 与函数名之间带有(*)
- 函数体内部使用 yield 表达式,函数执行遇到 yield 就返回
场景
var readFile = function (name, ms) {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log(name + '读完了')
resolve()
}, ms)
})
}
var gen = function* () {
console.log('指定generator')
yield readFile('first', 1000)
yield readFile('second', 2000)
yield readFile('third', 3000)
yield readFile('forth', 4000)
yield readFile('fifth', 5000)
return '完成了'
}
var g = gen()
var result = g.next()
result.value
.then(() => {
g.next()
})
.then(() => {
g.next()
})
.then(() => {
g.next()
})
.then(() => {
g.next()
})
优点:
- 可以控制函数的执行,可以配合 co 函数库使用
缺点:
- 流程管理却不方便(即何时执行第一阶段、何时执行第二阶段)
async 和 await
场景 1
var readFile = function (name, ms) {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log(name + '读完了')
resolve()
}, ms)
})
}
async function useAsyncAwait() {
await readFile('first', 1000)
await readFile('second', 2000)
await readFile('third', 3000)
await readFile('forth', 4000)
await readFile('fifth', 5000)
console.log('async文件阅读完毕')
}
useAsyncAwait()
优点
-
内置执行器。意味着不需要像 generator 一样调用 next 函数或 co 模块
-
更广的适用性。async 和 await 后面跟的都是 promise 函数,原始数据类型会被转为 promise
-
语义更清晰、简洁
缺点
-
大量的 await 代码会阻塞(程序并不会等在原地,而是继续事件循环,等到响应后继续往下走)程序运行,每个 await 都会等待前一个完成
场景 2 场景 1 中的代码,其实 second,third 的伪请求其实并不依赖于 first,second 的结果,但它们必须等待前一个的完成才能继续,而我们想要的是它们同时进行,所以正确的操作应该是这样的。
async function useAsyncAwait() {
const first = readFile('first', 1000)
const second = readFile('second', 2000)
const third = readFile('third', 3000)
const forth = readFile('forth', 4000)
const fifth = readFile('fifth', 5000)
console.log('async文件阅读完毕')
await first
await second
await third
await forth
await fifth
}
useAsyncAwait()
在这里,我们将三个 promise 对象存储在变量中,这样可以同时启动它们关联的进程。
总结
在这篇文章中,我们已经介绍了 JavaScript 异步发展史中 --- callback、promise、generator、async/await 的使用方式、优点与缺点。
发展史 | 优点 | 缺点 | callback | 解决了同步问题 | 回调地狱、可读性差、无法 try / catch 、无法 return | promise | 一定程度上解决了回调地狱的可读性 | 无法取消、任务多时,同样存在语义不清晰 | generator | 可以控制函数的执行,可以配合 co 函数库使用 | 流程管理却不方便(即何时执行第一阶段、何时执行第二阶段 | async/await | 语义更清晰、简洁,内置执行器 | 认知不清晰可能会造成大量 await 阻塞(程序并不会等在原地,而是继续事件循环,等到响应后继续往下走)情况 |
---|
而在现有的异步解决方案中,async/await 是使用人数最多的,它带给我们最大的好处即同步代码的风格,语义简洁、清晰。
相关文章
- MDN-异步 JavaScript
- 阮一峰-JavaScript 异步编程的 4 种方法
- 掘金-细说 JavaScript 异步的发展历程
- 掘金-async/await 的优点、陷阱以及如何使用
- JS 异步发展流程
常见问题FAQ
- 免费下载或者VIP会员专享资源能否直接商用?
- 本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
- 提示下载完但解压或打开不了?
- 找不到素材资源介绍文章里的示例图片?
- 模板不会安装或需要功能定制以及二次开发?
发表评论
还没有评论,快来抢沙发吧!