第 12 题:JS 异步解决方案的发展历程以及优缺点
代码是自上而下同步执行的,既后面的代码必须等待前面的代码执行完才会执行,而异步执行则是将主线程中的某段代码交由子线程去执行,当交给子线程后,主线程就会继续执行后面代码,而不用等待子线程执行完成,异步是程序语言并行执行的一种手段,通常将耗时的任务交由子线程同时处理,从而提升整体任务耗时。
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(25)
还是会有点影响,这样才不会阻塞
async function test() {
let a = fetch('XXX1',)
let b = fetch('XXX2',)
let c = fetch('XXX3',)
}
我试了一下打印出的还是2,11啊
@inJs 虽然这三个异步操作没有依赖关系,那要是函数内下面的某个操作需要他们三个全部返回才能处理怎么办呢?所以还是得用await,否则连async函数都没必要写。
@wfymalong 如果把异步函数提前赋值给变量,就不需要用Promise.all了吧,人家给的例子是正确的。
没人提rxjs吗。
ReactiveX是一种针对异步数据流的编程。简单来说,它将一切数据,包括HTTP请求,DOM事件或者普通数据等包装成流的形式,然后用强大丰富的操作符对流进行处理,使你能以同步编程的方式处理异步数据,并组合不同的操作符来轻松优雅的实现你所需要的功能 。rxjs是ReactiveX的javascript实现。
对rxjs简单的理解,就是把一系列的异步行为用时间维度的数组表示出来,将行为视为同步数据一般进行处理,编排与更新。
promise只有成功和失败两个结果,且一旦开始不能中止。rxjs则可以有多个成功结果,可以随时中止或进行其他分叉处理。
promise通常只能对串行、并发的多个异步行为进行简单的异步控制,一旦叠加其他维度条件控制则力不从心。
比如promise在实际应用中会因接口响应慢而导致竞态条件问题。rxjs可以轻松的通过switchMap操作符解决。
AsyncFunctionBody 中遇到 await 关键字的时候,会解析成 AwaitExpression 并等待其返回结果。上面代码中,对表达式
a + await 10
进行求职的时候,第一个 await 出现之前的代码是同步代码,此时 a 通过 RHS 得到的值是 0相关流程可以感受下:
另外,书写时推荐 await 放在表达式的开头。ESLint 规则:https://cn.eslint.org/docs/rules/require-atomic-updates
Rxjs
你这样的写法a = a + c已经在一个微任务队列中,这时候由于同步任务a++先执行,导致a被读取时为1,你的写法等价于
promise的缺点有三个:
1、promise一旦新建,就会立即执行,无法取消
2、promise如果不设置回调函数,promise内部抛出的错误,不会反应到外部
3、promise处于pending状态时,无法得知目前进展到哪一阶段,刚开始执行还是即将完成
上一个console,a++之后的,变成1了;下一个console,tmp存的是a++之前的,值为0
@sisterAn 其实最后async await代码可以翻译成下面这样就清爽了:
async方法执行时,遇到await会立即执行表达式,然后把表达式后面的代码放到微任务队列里,让出执行栈让同步代码先执行
最早的异步的实现应该:
1.回调函数 缺点: 回调地狱,不利于维护
2. promise 可以链式调用了 解决了回调地狱, 但是无法取消promise 一旦开启只有pending resolve reject 不能取消
3.generator
yield next
4.async await 不是所有场景都使用 注意性能问题 可以用try catch 捕获异常,将异步代码改成同步代码,如果多个操作没有依赖性 会造成性能问题
上面说了执行到 await 的时候会保留 堆栈中的东西,这个时候变量a并没有使用,所以并没有保留 a = 0;当 await 结束后,再使用变量a,此时a的值经过 a++ 已经变成了 1 了。所以最后输出的是11。
我尝试以下代码片段,按照您这样模拟是行不通的,不太理解为什么我这样写async里面输出就是 11:
Promise 如果在这样写
Promise.resolve().then(function() { return new Promise(function() {}) })
这样写的话Promise就可以中止Promise执行链,相当于取消Promise了
需要好好理解的是async, await VS Generator改进了什么
callback->Promise->generator->async/await
其实提到了 Promise.al 就说明这里有个隐含前提,尽管三个 fetch 之前没有依赖,但是需要等待三个 fetch 都已经完成了再执行下一句。
@duanzheng
若 b 里直接访问 a ,即写为
a = a + r
,则最后then打印'a' 11
,因为会先执行a++
。我猜他应该是为了保持最后的打印结果是'a' 10
,所以才使用了一个变量缓存最初a的值吧。个人见解请教一下,为何 b 里不是直接访问 a?
@sisterAn 规范里好像没提到 await 内部实现了 generator, 如果从 polyfill 中的实现去断定 await 内部就是 generator……这样好像有点不严谨……
另外
await
的例子其实可以转换为@sisterAn 没有依赖关系的异步操作不使用
await
就没有你所说的性能问题了。 比如:JS 异步已经告一段落了,这里来一波小总结
1. 回调函数(callback)
缺点:回调地狱,不能用 try catch 捕获错误,不能 return
回调地狱的根本问题在于:
优点:解决了同步的问题(只要有一个任务耗时很长,后面的任务都必须排队等着,会拖延整个程序的执行。)
2. Promise
Promise就是为了解决callback的问题而产生的。
Promise 实现了链式调用,也就是说每次 then 后返回的都是一个全新 Promise,如果我们在 then 中 return ,return 的结果会被 Promise.resolve() 包装
优点:解决了回调地狱的问题
缺点:无法取消 Promise ,错误需要通过回调函数来捕获
3. Generator
特点:可以控制函数的执行,可以配合 co 函数库使用
4. Async/await
async、await 是异步的终极解决方案
优点是:代码清晰,不用像 Promise 写一大堆 then 链,处理了回调地狱的问题
缺点:await 将异步代码改造成同步代码,如果多个异步操作没有依赖性而使用 await 会导致性能上的降低。
下面来看一个使用
await
的例子:对于以上代码你可能会有疑惑,让我来解释下原因
b
先执行,在执行到await 10
之前变量a
还是 0,因为await
内部实现了generator
,generator
会保留堆栈中东西,所以这时候a = 0
被保存了下来await
是异步操作,后来的表达式不返回Promise
的话,就会包装成Promise.reslove(返回值)
,然后会去执行函数外的同步代码a = 0 + 10
上述解释中提到了
await
内部实现了generator
,其实await
就是generator
加上Promise
的语法糖,且内部实现了自动执行generator
。如果你熟悉 co 的话,其实自己就可以实现这样的语法糖。本文首发于我的博客:JS异步解决方案的发展历程以及优缺点