• 异步进阶


    相关知识点

    • JS 是单线程运行的

    • 异步要基于回调来实现

    • event loop 就是异步回调的实现原理

    JS 是如何执行?

    • 从前到后,一行一行执行

    • 如果某一行执行报错,则停止下面代码的执行

    • 先把同步代码执行完,再执行异步

    下图所示,在JS引擎中,Call Stack是调用栈,Web APIS是在ES6规范之外,浏览器定义的,event loop是事件轮询,callback Queue回调函数队列。

    执行顺序:

    1. 同步代码,一行一行的放在Call Stack中执行

    2. 遇到异步,先Web APIS记录下,然后等待时间(定时,网络请求等)

    3. 等待完毕,就移动到Callback Queue

    4. 如果Call Stack为空,即同步代码执行完毕,event loop开始工作

    5. 轮询查找Callback Queue,如果有代码则移动到Call Stack中执行

    6. 然后继续轮询查找

    Promise

    Promise三种状态

    • pending( 等待中 )

    • resolved(成功)

    • rejected(失败)

    pengding ——> resolved 或 pengding ——> rejected

    Promise变化是不可逆的

    状态的表现和变化

    pengding 状态,不会触发 thencatch

    resolved 状态,会触发后续的 then 回调函数

    rejected 状态,会触发后续的 catch 回调函数

    then 和 catch 对状态的影响

    then / catch 正常返回 resolved ,里面有报错则返回rejected

    const p1 = Promise.resolve().then(()=>{
      return 100
    })
    console.log('打印一下p1',p1) 
    p1.then(()=>{
      console.log('在p1 中 resolved 触发 后续 then 回调')
    })
    ​
    const p2 = Promise.resolve().then(()=>{
      throw new Error('P2抛出来的错误')
    })
    p2.then(()=>{
      console.log('p2的状态是 reject 不会走then回调函数,所以不会打印这一行',p2)
    }).catch( err =>{
      console.log('p2的打印的错误信息',err)
    } )
    ​
    const p3 = Promise.reject('my error').catch(err =>{
      console.log('p3抛出的错误',err)
    })
    p3.then(()=>{
      console.log('p3在catch之后状态是resolved,所以使用then')
    })
    ​
    const p4 = Promise.reject('my error').catch(err =>{
      throw new Error('P4抛出来的错误')
    })
    p4.then(()=>{
      console.log('p4在catch中抛出错误,所以现在的状态是reject,不会走then')
    }).catch(()=>{
      console.log('p4的状态是reject')
    })

    then/catch三道面试题

    Promise.resolve().then(()=>{
        console.log(1) // 1
    }).catch(()=>{
        console.log(2)
    }).then(()=>{
        console.log(3) // 3
    })
    Promise.resolve().then(()=>{
        console.log(1) // 1
        throw new Error('erro1')
    }).catch(()=>{
        console.log(2) // 2 
    }).then(()=>{
        console.log(3)
    })
    Promise.resolve().then(()=>{
        console.log(1) // 1
        throw new Error('erro1')
    }).catch(()=>{
        console.log(2) // 2 
    }).catch(()=>{
        console.log(3)
    })

    async/await

    异步回调容易导致callback hell,Promise then catch 链式调用虽然把层级铺开,但也是基于回调函数。

    async/await 是用同步的语法来完成异步代码,彻底消灭回调函数。

    async/await 和 Promise 的关系

    async/await 可以彻底消灭异步回调,但和 Promise 并不互斥,两者是相辅相成的。

    • 执行 async 函数,返回的是 Promise 对象

    • await 相当于 Promise 的then

    • try...catch 可以捕获异常,代替了Promise 的 catch

    // 执行 async 函数,返回的是一个 Promise 对象
    async function fn1(){
      // return 100; //相当于 return Promise.resolve(100)
      return Promise.resolve(200)
    }
    const res1 = fn1() 
    // console.log('res1',res1)
    res1.then(data=>{
      console.log('data',data) // 200
    })
    
    
    // 如果 await 后面跟的是 Promise ,它就会当成 Promise 来使用;如果 await 后面跟的是一个值,会将其当封装成 Promise 的形式,去执行 Promise 的 then,然后返回
    !(async function(){
      const p1 = Promise.resolve(300)
      const data = await p1 // await 相当于 Promise then 
      console.log('data',data)
    })()
    
    !(async function (){
      const data1 = await 400 // await Promise resolve(400)
      console.log('data1',data1)
    })()
    
    !(async function (){
      const data2 = await fn1() 
      console.log('data2',data2)
    })()
    // try...catch 可以捕获异常,代替了Promise 的 catch
    !(async function (){
      const p4 = Promise.reject('err1') // rejected 状态
      try {
        const res = await p4
        console.log(res)
      } catch (ex) {
        console.log(ex) // try..catch 相当于 Promise catch
      }
    })()
    
    !(async function (){
      const p4 = Promise.reject('err1') // rejected 状态
      const res = await p4 // await -> then 
      console.log('res',res)
    })()

    async/await的题目

    await的后面都可以看作是callback的内容

    async function async1 (){
      console.log('async1 start') // 第二步
      await async2()
      console.log('async1 end') // 第五步
    }
    
    async function async2 () {
      console.log('async2') // 第三步
    }
    
    console.log('srcipt start') // 第一步
    async1()
    console.log('srcipt end') // 第四步 同步代码执行完毕
    async function async1 (){
      console.log('async1 start') // 第二步
      await async2()
      // 以下是 callback 的内容
      console.log('async1 middle') // 第五步
      await async3()
      console.log('async1 end') // 第七步
    }
    
    async function async2 () {
      console.log('async2') // 第三步
    }
    
    async function async3 () {
      console.log('async3') // 第六步
    }
    
    console.log('srcipt start') // 第一步
    async1()
    console.log('srcipt end') // 第四步 同步代码执行完毕

    for...of

    function muti(num) {
      return new Promise(resolve => {
        setTimeout(()=>{
          resolve(num * num)
        },500)
      })
    }
    
    const nums = [1,2,3]
    
    // 0.5秒之后执行三次
    // nums.forEach(async(i)=>{
    //   const res = await muti(i)
    //   console.log(res)
    // })
    
    //每隔0.5s执行一次
    !(async function(){
      for(let i of nums ){
        const res = await muti(i)
        console.log(res)
      }
    })()

    微任务和宏任务

    微任务和宏任务是异步里api的分类

    异步的执行顺序和出场顺序有关,但不同类型的异步执行顺序和出场顺序无光。

    • 宏任务:setTimeout、setInterval、Ajax、DOM事件

    • 微任务:Promise、async/await

    微任务执行时机比宏任务要早

    console.log(100);
    // 宏任务
    setTimeout(()=>{
      console.log(200);
    })
    // 微任务
    Promise.resolve().then(()=>{
      console.log(300);
    })
    console.log(400);

    event loop 和 DOM 渲染

    步骤:

    1. call stack 空闲的时候(同步代码执行完毕)

    2. 执行微任务

    3. 再进行dom渲染

    4. 执行宏任务

    5. 执行event loop

    6. 将call Queue 队列里的回调函数添加到call stack里执行

    7. 重复第一步

    每次 call stack清空(即每次轮询结束),即同步任务执行完,都是DOM重新渲染的机会,DOM结构如有改变则重新渲染,然后再去触发下一次 event loop。

    微任务和宏任务的区别:

    • 宏任务:DOM渲染后触发,如setTimeout

    • 微任务:DOM渲染前触发,如Promise

     

    // 修改 DOM
    const $p1 = $('<p>一段文字</p>')
    const $p2 = $('<p>一段文字</p>')
    const $p3 = $('<p>一段文字</p>')
    $('#container')
        .append($p1)
        .append($p2)
        .append($p3)
    
    // // 微任务:渲染之前执行(DOM 结构已更新)
    // Promise.resolve().then(() => {
    //     const length = $('#container').children().length
    //     alert(`micro task ${length}`)
    // })
    
    // 宏任务:渲染之后执行(DOM 结构已更新)
    setTimeout(() => {
        const length = $('#container').children().length
        alert(`macro task ${length}`)
    })

    深入思考:为何两者会有以上区别,一个在渲染前,一个在渲染后?

    • 微任务:ES 语法标准之内,JS 引擎来统一处理。即,不用浏览器有任何关于,即可一次性处理完,更快更及时。

    • 宏任务:ES 语法没有,JS 引擎不处理,浏览器(或 nodejs)干预处理。

    题目

    1.描述event loop(事件循环/事件轮询)的机制,可画图

    上图代码执行顺序:

    1. 将 console.log("Hi") 推入调用栈,调用栈会执行代码

    2. 执行代码,控制台打印“Hi”,调用栈清空

    3. 执行 setTimeout,setTimeout由浏览器定义,不是ES6的内容;将定时器放到Web APIs中,到时间后将回调函数放到回调函数队列中

    4. 执行完了setTimeout, 清空调用栈

    5. console.log("Bye")进入调用栈,执行,调用栈清空

    6. 同步代码被执行完,,回调栈空,浏览器内核启动时间循环机制

    7. 五秒之后,定时器将cb1推到回调函数队列中

    8. 事件循环将cb1放入调用栈

    9. 执行cb1,将console.log("cb1")放入调用栈

    10. console.log(“cb1”)出栈,执行,打印“cb1”

    11. cb1函数只有一行,已经执行完了,也就可以清空了

    12.  

  • 相关阅读:
    spring boot 启动后执行初始化方法
    Linux CentOS 7 下 JDK 安装与配置
    Linux rpm 命令参数使用详解[介绍和应用]
    异常处理: 重载Throwable.fillInStackTrace方法已提高Java性能
    dubbo 配置属性
    centos7 操作防火墙
    springBoot 打包 dubbo jar包
    直播中聊天场景的用例分享
    解决在安装Fiddler4.6版本后,在手机上安装证书出现的问题解决方法
    系统调优方案思路分享
  • 原文地址:https://www.cnblogs.com/manhuai/p/14305722.html
Copyright © 2020-2023  润新知