异步进阶

相关知识点

  • 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.  

posted @ 2021-01-20 23:27  倦梦还  阅读(79)  评论(0编辑  收藏  举报