# JS 异步编程

https://github.com/Advanced-Frontend/Daily-Interview-Question/issues/7
讲的非常好

<img src="https://eucli-1314359160.cos.ap-beijing.myqcloud.com/test/image-20231029094133469.png" alt="image-20231029094133469" style="zoom:80%;" />

# EventLoop

JavaScript 是单线程的,包含了同步任务与异步任务,同步任务直接在调用栈 (主线程) 中执行,异步任务会放入任务队列 (TaskQueue) 中,等待同步任务全部执行完毕再取出来,所以如果异步之中仍有异步任务,在调用栈中执行时会继续放入任务队列中,这就是 JS 的事件循环机制 (EventLoop)。

# 宏任务与微任务

异步任务队列实际上分为两种,分别是宏任务队列与微任务队列,在当前循环中会优先执行微任务,当微任务队列被清空后才会执行宏任务队列。

微任务实际上是宏任务的其中一个步骤。微任务是 JS 级别的,宏任务是宿主级别的,是包含关系,不是先后关系。

# 微任务(micro task):

  • promise .then / .catch / .finally

  • process.nextTick

  • MutationObserver

  • async/await : 本质上还是基于 Promise 的一些封装,而 Promise 是属于微任务的一种。所以在使用 await 关键字与 Promise.then 效果类似。

    async 函数在 await 之前的代码都是同步执行的,可以理解为 await 之前的代码属于 new Promise 时传入的代码,await 之后的所有代码都是在 Promise.then 中的回调

# 宏任务(macro task):

  • script(整体代码)

  • 定时器系列: setTimeoutsetIntervalsetImmediate

  • I/O :这种比较耗性能的操作浏览器会交给单独的线程去办,得到结果后再通知回来

  • requestAnimationFrame

tips:UI 渲染不是宏任务,而是和微任务平行的一个操作步骤

# 执行顺序:

同步任务 > 微任务 > requestAnimationFrame > DOM渲染 > 宏任务

构造函数是同步任务

# 总结:

  1. 先执行上下文栈的 同步任务
  2. 看一下微任务队列有没有需要执行的微任务,按照队列 先进先出 的原则,** 一次执行完所有 ** 微任务队列任务;
  3. 执行宏任务队列, 一次执行一个 ,每执行完一个都会检测微任务是否为空
  4. 不为空则先执行微任务
  5. 为空再执行下一个宏任务;

1699173401134-2023-11-5.png

1699174167689-2023-11-5.png

# MutationObserver:是一个内建对象,它观察 DOM 元素,并在检测到更改时触发回调

# RequestAnimationFrame

  • 仅对浏览器生效,回调属于高优先级任务
  • 会将每一帧中所有 DOM 操作集中一次渲染(所以性能较好)
  • 回流重绘的时间会跟随刷新频率动态改变,不能主动控制(由于是帧操作,必须递归调用)
  • 因为是异步任务,在调用后实际还可以取消
  • 浏览器页面不是激活状态下(或离开标签页),会自动暂停执行
  • 根据以上特性该方法常用于处理帧动画的操作,性能优于 setInterval

# RequestIdleCallback

该回调函数是低优先级的任务,仅在浏览器空闲时期被调用(目前仍处于实验功能阶段,在微前端中常有应用)

# Callback

容易造成回调地狱,不能 try...catch 捕获,不能 return

# Promise

也会造成回调地狱,但优化了 callback 方式的回调地狱问题,而 asyncawait (ES7)才真正解决了异步回调的问题。

# 面试高频:

  • Promise.all :将一个包含 Promise 实例的数组传入,数组内所有 Promise 实例执行完毕时,该方法会返回结果数组。

  • Promise.race :返回的是最快成功回调的一个结果。

  • 实现 Promise.all 的思路:返回一个 promise 对象,方法中设有变量 count、results,循环执行每个 promise,then 回调中 count++,当 count === promises.length 时 resolve (results)

b 站讲解:https://www.bilibili.com/video/BV1WP4y187Tu/?spm_id_from=333.337.search-card.all.click&vd_source=ff247772a62ee9c3b2ed27fa5e4a91e2

# 异步

js 中异步有两种方式实现:

  1. 回调函数,如 setTimeOut。但是会回调地狱
  2. Promise。“承诺”:承诺会在未来的某个时刻返回数据。链式调用,避免代码的层层嵌套

# async/await 是什么

async/await 是 ES2017 (ES8) 提出的基于 Promise 的解决异步的最终方案,是一个语法糖。
async 是 “异步” 的简写,而 await 可以认为是 async wait 的简写。所以应该很好理解 async 用于申明一个 function 是异步的,而 await 用于等待一个异步方法执行完成。

一个问题:await 只能出现在 async 函数中,那这个 async 函数应该怎么调用?如果需要通过 await 来调用一个 async 函数,那这个调用的外面必须得再包一个 async 函数,然后…… 进入死循环,永无出头之日……
async 函数返回的是一个 Promise 对象,所以在最外层不能用 await 获取其返回值的情况下,我们当然应该用原来的方式:then () 链来处理这个 Promise 对象

# async

async 是一个加在函数前的修饰符,被 async 定义的函数会默认返回一个 Promise 对象 resolve 的值。因此对 async 函数可以直接 then,返回值就是 then 方法传入的函数。

# await

await 也是一个修饰符,只能放在 async 定义的函数内。可以理解为等待。可以不再使用 then 直接使用 await

  • await 到底在等啥?
    一般来说,都认为 await 是在等待一个 async 函数完成。不过按语法说明,await 等待的是一个表达式,这个表达式的计算结果是 Promise 对象或者其它值(换句话说,就是没有特殊限定)。
    因为 async 函数返回一个 Promise 对象,所以 await 可以用于等待一个 async 函数的返回值 —— 这也可以说是 await 在等 async 函数,但要清楚,它等的实际是一个返回值。注意到 await 不仅仅用于等 Promise 对象,它可以等任意表达式的结果,所以,await 后面实际是可以接普通函数调用或者直接量的。

  • await 等到了要等的,然后呢?
    await 等到了它要等的东西,一个 Promise 对象,或者其它值,然后呢?我不得不先说,await 是个运算符,用于组成表达式,await 表达式的运算结果取决于它等的东西。

  1. 如果它等到的不是一个 Promise 对象,那 await 表达式的运算结果就是它等到的东西。
  2. 如果它等到的是一个 Promise 对象,await 就忙起来了,它会阻塞后面的代码,等着 Promise 对象 resolve,然后得到 resolve 的值,作为 await 表达式的运算结果。

    看到上面的阻塞一词,心慌了吧…… 放心,这就是 await 必须用在 async 函数中的原因。async 函数调用不会造成阻塞,它内部所有的阻塞都被封装在一个 Promise 对象中异步执行。

——————————————————
使用 async 和 await 可以让我们写出更清晰、更容易理解的异步代码,有了它们之后,我们几乎不再需要使用底层的 Promise 对象,包括调用它的 then (),catch () 函数等等

更新于

请我喝[茶]~( ̄▽ ̄)~*

 微信支付

微信支付

 支付宝

支付宝

 贝宝

贝宝