JS 异步
什么是异步
1 | setTimeout(()=>{console.log("路")},1000); |
在1000s的时间(需要较长时间响应的事件)内执行了下一块代码。
正确说法:任何时候,只要把一段代码包装成一个函数,并指定它在响应某个事件(定时器、鼠标点 击、Ajax 响应等)时执行,你就是在代码中创建了一个将来执行的块,也由此在这个程序 中引入了异步机制。
传统的实现异步的方式
- 回调
- 其他
为什么我们会需要使用异步,因为我们不想浏览器发生阻塞,想象我们手机卡住的场景,这简直难以忍受;但是通过回调引发的异步同样会引发新的问题(当然,这些问题与引起的阻塞相比都是小问题)。(其中影响最大的“信任问题”。)
回调引发的问题
顺序问题->追踪难度加大
1 | doA(function(){ |
或许能够追踪到A → B → C → D → E → F这样的执行顺序,可是真正的异步实际情况会复杂很多。而且在某些情况下,doA顺序执行了,而在你的代码中,函数的执行结果很依赖于各个函数的执行顺序,这时候你很难追踪到是那个部分出了问题。当然也可以通过硬编码(调整函数代码块的位置和设置if语句)去排除这些问题,但是这样就将代码复杂化了。所以我们需要更好的异步模式。
信任问题
1 | //A |
有时候 ajax(..)
(也 就是你交付回调 continuation 的第三方)不是你编写的代码,也不在你的直接控制下。多数情况下,它是某个第三方提供的工具。我们把这称为控制反转(inversion of control),也就是把自己程序一部分的执行控制交给某个第三方。(出现的不可信任的第三方)。
由于某些原因,c没有回调成功,执行ajax这个工具进行了会在五秒钟内每秒重试一次传入的回调函数,然后才会因超时而失败。之后这5个回调成功了,c被执行了5次。如果c是一个支付相关的,则用户会被扣款五次。当然这也可以通过设置if条件,防止第三方工具瞎搞。出了重复回调外,还会有其他问题
- 调用回调过早(在追踪之前)
- 调用回调过晚(或没有调用)
- 调用回调的次数太少或太多(就像你遇到过的问题!)
- 没有把所需的环境 / 参数成功传给你的回调函数
- 吞掉可能出现的错误或异常
为了解决这一系例隐患,我们不知道要增加多少工作量,所以我们迫切需要新的异步模式!
新的异步模式Promise
在 ES6 中,有一个新的概念建立在事件循环队列之上,叫作任务队列(job queue)。事件循环队列类似于一个游乐园游戏:玩过了一个游戏之后,你需要重新到队尾排队才能 再玩一次。而任务队列类似于玩过了游戏之后,插队接着继续玩。一个 Promise (p1)决议后,这个 Promise 上所有的通过 then(..) 注册的回调(cb1,cb2)都会在下一个异步时机点(p2 cb3)上依次被立即调用。这些回调中的任意一个(cb1)都无法影响或延误对其他回调(cb2)的调用。由Promise创建的then异步是基于任务的,而不像cb是基于事件的。
什么是Promise
- promise作为一个返回值
1 | function foo(x){ |
- promise事件
1 | //相比回调,实现了控制反转,bar不再通过foo的成功调用 |
理解then(…)
具有then()函数的对象,都是promise吗?不是。普通then()与promise产生的then的区别?
1 | var p = { |
Promise如何解决了之前的问题,你看出来了吗
关于信任问题
1 | //Promise 构造器 |
回调未调用
reject事件
但如果Promise永远不被决议呢,Promise提供了解决方案,一种称为竞态的解决方案调用次数过多或过少
由于 Promise 只能被决议一次,且决议后的结果不会被更改,所以任何通过 then(..) 注册的(每个)回调就只会被调用一次。如果你把同一个回调注册了不止一次(比如p.then(f); p.then(f);),那它被调用的次数就会和注册次数相同调用过晚
在下一个异步点之前一定会结束这个任务。未能传递参数/环境值
依靠注册的回调传递吞掉错误或异常
reject能捕捉到
1 | var p = new Promise( function(resolve,reject){ |
如何将传统的回调异步转换为Promise异步模式
1 | //回调 |
Promise的不足
其一
为了避免丢失被忽略和抛弃的 Promise 错误,一些开发者表示,Promise 链的一个最佳实践 就是最后总以一个 catch(..) 结束,比如
1 | var p = Promise.resolve(42); |