使用并深入Javascript的异步机制
也没个分级标题,烦啊,谁去鼓捣你那字体大小啊

js运行时:上下文、事件循环
js在设计之初被设计为单线程语言,而现代浏览器的js环境支持多线程应用,其中使用了一些方法突破了传统单线程语言的限制。
js在执行时运行在执行上下文当中,代码主体存在于全局上下文,每个函数存在于自己的本地上下文,eval()
函数会创建一个新的执行上下文。上下文本质上就是作用域,每个代码段开始执行时会创建新的上下文,代码退出时销毁该上下文。多个上下文之间的关系类似x86的调用栈。
js运行时维护了一组代理以执行js代码,每个代理由以下几部分构成:
执行上下文的集合、执行上下文栈
主线程、可能创建用于执行worker的额外线程集合
一个任务队列、一个微任务队列
除了主线程外,代理的其他组成部分对该代理来说都是唯一的。
每个代理由事件循环所驱动,事件循环负责收集事件、排列任务,然后执行等待中的js宏任务,再执行微任务,最后执行一些渲染绘制操作,然后进入下一个循环。web应用和浏览器的GUI运行在主线程当中,共享事件循环。事件循环分为三类:
Window事件循环:驱动所有同源窗口。
Worker事件循环:驱动worker的事件循环,包括web worker/shared worker/service worker,Worker被放在一或多个代理中,独立于代码主体,被浏览器以一或多个事件循环处理。
Worklet事件循环:驱动worklet的代理。

任务(宏任务)与微任务
当执行来自任务队列中的任务时,在每一次新的事件循环开始迭代的时候运行时都会执行队列中的每个任务。在每次迭代开始之后加入到队列中的任务需要在下一次迭代开始之后才会被执行.
每次当一个任务退出且执行上下文为空的时候,微任务队列中的每一个微任务会依次被执行。不同的是它会等到微任务队列为空才会停止执行——即使中途有微任务加入。换句话说,微任务可以添加新的微任务到队列中,并在下一个任务开始执行之前且当前事件循环结束之前执行完所有的微任务。
顺序总结
如果新任务是宏任务,添加到新的宏任务队列中
如果新任务是微任务,添加到微任务队列中
执行当前宏任务队列,当需要添加一个新任务时:
当前宏任务队列执行结束
执行微任务队列,新的微任务会被添加到当前微任务队列中继续执行
当前微任务队列执行结束,切换到新的宏任务队列重复上述步骤
宏任务

微任务

二者对比


Promise
Promise具有三种状态,pending、fulfilled、rejected:
pending:Promise的初始状态,既没有被兑现也没有被拒绝,表示请求还在进行中
fulfilled:已兑现,表示请求成功完成,调用
then()
处理函数,Promise会向其中传入包含服务器响应的Response对象rejected:已拒绝,表示请求失败,调用
catch()
处理函数fulfilled和rejected有时使用settled状态来概括表示(与pending对应)。
Promise.all()
方法接收一个Promise array/map/set(下用数组代指),返回一个单一的Promise,用于实现多个异步函数的调用,当且仅当数组中所有Promise都被兑现时,才会调用then()
并传入包括了全部响应的数组,其顺序与传入all()
的Promise顺序相同;数组中任何一个Promise被拒绝时都会调用catch()
,并传入该Promise抛出的错误。
Promise.any()
方法与Promise.all()
方法相对应,当且仅当数组中所有Promise都被拒绝时,才会调用catch()
;当任何一个Promise被兑现时都会调用then()
async/await
在函数开头添加async可以使其成为一个异步函数;在异步函数中,可以在调用返回Promise的函数前使用await,此时代码会等待直到Promise完成,由此实现异步函数的同步阻塞。async函数的返回值总是一个Promise对象
async/await本质是Promise的封装,await后面的代码相当于Promise.then的回调
await fetch()
会直接返回response对象,使用await时使用try-catch方式捕获异常

Promise()构造器
Promise()
构造器使用单个函数作为参数。该函数称作executer。当创建新的promise时需要实现这个执行器。executer接收两个函数作为参数,两个参数通常被称作resolve
和reject
。
在executer中,手动进行异步函数的调用以及resolve和reject的调用,如果executer出现异常则自动调用reject。