title: 异步详情
date:
tags:

  • JS
  • JavaScript
  • 前端
    description: 这是一篇对于异步的解读,可以帮助你快速学会并使用基本的异步

异步详解

导读

首先,要知道 JavaScript 是一门单线程的语言,要搞明白其为何会设计成单线程,要先来说一下 JavaScript 的最初应用场景。

其次,在单线程语言中如何处理多线程的任务。

再次,同步和异步的流程以及如何理解异步。

然后,在解决复杂的逻辑业务时,出现了什么样的问题。

再然后,怎样解决这一问题。

=> Promise/.then(),async/await,

最后谈谈微任务及宏任务

JavaScript 最初的应用场景

最初,JavaScript 的设计是作为浏览器脚本语言实现用户的交互,而为了避免多线程给我们带来很多不必要的麻烦,比如说:一个线程在删除一个节点,而另一个线程在修改这个节点,这时我们应该以哪一条线程为主呢?

因此,设计者将其设计成单线程的语言。

在 H5 中规定了 JS 可以拥有多个子线程,但是子线程仍然是依托于主线程的,且不能够操作节点。

为什么要使用多线程

  1. 单线程的缺点:
    单线程有一个“致命”的缺点,就是会造成阻塞;
    因为是单线程,所以在程序运行时是按照先进先出的原则来进行任务处理的,也正是因如此,在主线程遇到了耗时操作后,其后的任务就进入到了等待的状态。如果此时 CPU 是被占用的,也没什么,但是如果这个耗时任务是一个不占 CPU 的操作,举个栗子:向服务端请求数据。这个时候 CPU 出入空闲状态但是程序并没有执行结束,这就形成了阻塞。

  2. 启发:
    在进行耗时操作且影响代码正常运行时,我们可以先不管这个操作,将其挂起。先处理后面的任务,等到主线程清空时,再来执行这个任务。于是出现了同步任务和异步任务。

同步和异步及程序运行流程

关于这一块内容,笔者建议从整体来看,将这一块中的内容结合起来读,可能会更好理解

  1. 我们可以将同步任务理解为在主线程中执行的任务,异步任务理解为在子线程执行。
  2. 同步任务和异步任务的执行过程:

    • 同步任务正常运行,没有特殊情况会一直执行完毕;
    • 当主线程运行到异步任务时,会安排一个子线程去运行异步任务,当异步任务运行结束后,向任务队列发送一个事件。表示该异步任务可以进入主线程执行了。
  3. 当不考虑下面讲的微任务和宏任务时,我们的程序运行时,会先执行同步任务,执行到异步任务后,将其发送到子线程中运行,运行结束会向任务队列发送一个事件。同步任务执行完毕即主线程清空后,主线程会向任务队列询问,是否有接收到事件,如果没有,那么主线程会一直询问,这个过程称为event loop;如果有,就执行这个事件。直到程序全部完成。

事件

在上文我们提到了事件这一概念,可以将事件理解为异步任务返回的一个回调函数,这个回调函数也就是事件会在主线程上运行。异步任务必须有回调函数。

拓展:异步函数必须 return 一个 Promise 对象

关于事件,阮一峰前辈是这么写的:

“任务队列”中的事件,除了 IO 设备的事件以外,还包括一些用户产生的事件(比如鼠标点击、页面滚动等等)。只要指定过回调函数,这些事件发生时就会进入”任务队列”,等待主线程读取。

“回调地狱”

在实际开发中,为了实现一些逻辑需求,可能会用到多层回调函数嵌套。这个时候会导致代码的可读性很差。人们称之为“回调地狱”

Promise 对象

  • 代表的是异步操作 最终完成或失败

  • 目的:将回调函数的多层嵌套形式,拆解成链式调用的形式。

  • 本质:函数返回的对象,在这个对象上绑定回调函数,避免从一开始将回调函数作为参数传入上一层函数。

  • 一个 Promise 必然会处于这几种状态:

    1. 待定(pending):初始状态,既没有被兑现,也没有被拒绝
    2. 已兑现(fulfilled):意味着操作成功完成 resolve
    3. 已拒绝(rejected):意味着操作失败 reject
  • Promise 的使用:一般作为函数的返回值 ```javascript const fn = function(){ return new Promise((resolve,reject)=>{
    1. if(ture){
    2. resolve(a);
    3. }else{
    4. reject(b);
    5. }
    }) }

fn() .then((res)=>{有返回值的函数}) .then((res)=>{有返回值的函数}) .then((res)=>{有返回值的函数}) … .then(res=>{最后的函数}) // 如果Promise对象中的请求完成了,那么将resolve的值传给then中的回调函数作为参数执行then方法 // 可以附加 .catch()在链式结构的末尾,来捕获错误(reject传回的值),并且之后的then不会执行 // 可以在最后加一个 .finally() 来执行清理操作 并且这个方法不管请求成功与否都会执行

  1. <a name="45b9901d"></a>
  2. ### async/await
  3. 是 Promise 的语法糖;让繁琐的 then(),和冗长的链式调用可读性变得更长些;
  4. **具体使用方式:**
  5. ```javascript
  6. //异步函数1
  7. function getData(data) {
  8. return new Promise((reslove) => {
  9. reslove(data);
  10. });
  11. }
  12. //异步函数2
  13. function sayHello(data) {
  14. return new Promise((reslove) => {
  15. reslove(data);
  16. });
  17. }
  18. //异步函数
  19. async function fn() {
  20. // await相当于.then() getData()相当于是回调函数
  21. // await必须在async修饰的函数体内使用
  22. const promiseA = await getData('info');
  23. const promiseB = await sayHello(promiseA);
  24. console.log(promiseB);
  25. }

谈谈微任务和宏任务

微任务

  1. 注意:Promise 对象中的代码是同步的,then()方法中的回调函数才是异步的
  2. then()中的是微任务

宏任务

  1. 定时器是宏任务

代码执行顺序口诀:先同步后异步,先微任务后宏任务

总之,同微宏