前端面试知识整理——JS异步进阶

本文深入探讨JavaScript的异步机制,包括Event Loop的工作原理,宏任务与微任务的区别,Promise的状态转换及其链式调用,以及Async/Await的使用方式和它们如何简化异步编程。通过实例分析,阐述了Promise与setTimeout、async函数与DOM渲染之间的执行顺序,帮助理解JavaScript的异步执行模型。
摘要由CSDN通过智能技术生成

前端面试知识整理——JS异步进阶

主要内容:
  • event loop
  • promise进阶
  • async/await
  • 微任务/宏任务
题目:

问答题:

  • 请描述event loop(事件循环/事件轮询)的机制,可画图
  • 什么是宏任务和微任务,有什么区别
  • promise 有哪三种状态?如何变化?

场景题1:promise then和catch的连接

// 第一题
Promise.resolve().then(() => {
    console.log(1)
}).catch(() => {
    console.log(2)
}).then(() => {
    console.log(3)
})
// 1 3

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

// 第三题
Promise.resolve().then(() => {
    console.log(1)
    throw new Error('erro1')
}).catch(() => {
    console.log(2)
}).catch(() => { // 注意这里是 catch
    console.log(3)
})
// 1 2

场景题2:async/await语法

async function fn() {
    return 100
}
(async function () {
    const a = fn() // ??               // promise
    const b = await fn() // ??         // 100
})()
;(async function () {
  console.log('start')
  const a = await 100
  console.log('a', a)
  const b = await Promise.resolve(200)
  console.log('b', b)
  const c = await Promise.reject(300) //报错,后面都不会执行
  console.log('c', c)
  console.log('end')
})()
// start   a 100     b 200

场景题3:promise和setTimeout的顺序

console.log(100)
setTimeout(() => {
  console.log(200)
})
Promise.resolve().then(() => {
  console.log(300)
})
console.log(400)
// 100 400 300 200

场景题4:外加async/await的顺序问题

async function async1() {
  console.log('async1 start') //2
  await async2() // 这一句会同步执行,返回 Promise ,其中的 `console.log('async2')` 也会同步执行
  console.log('async1 end') // 上面有 await ,下面就变成了“异步”,类似 cakkback 的功能(微任务) //6
}

async function async2() {
  console.log('async2') //3
}

console.log('script start') //1

setTimeout(function () {
  // 异步,宏任务
  console.log('setTimeout') //8
}, 0)

async1()
//初始化promise时,传入的函数会立即执行
new Promise(function (resolve) {
  // 返回 Promise 之后,即同步执行完成,then 是异步代码
  console.log('promise1') // Promise 的函数体会立刻执行 //4
  resolve()
}).then(function () {
  // 异步,微任务
  console.log('promise2') //7
})

console.log('script end') //5
// 同步代码执行完之后,屡一下现有的异步未执行的,按照顺序
// 1. async1 函数中 await 后面的内容 —— 微任务
// 2. setTimeout —— 宏任务
// 3. then —— 微任务

1.event loop(事件循环/事件轮询)

  • JS是单线程运行的
  • 异步要基于回调来实现
  • event loop就是异步回调的实现原理

JS如何执行:

  • 从前到后,一行一行执行
  • 如果某一行执行报错,则停止下面代码的执行
  • 先把同步代码执行完,在执行异步
      console.log('hi')
      setTimeout(function cb1() {
        console.log('cb1')
      }, 1000)
      console.log('bye')

总结 event loop过程

  • 同步代码,一行一行放在call stack中执行
  • 遇到异步,会先记录下,等待时机(定时,网络请求等)
  • 时机到了,就移动到callback Queue
  • 如果call stack为空(即同步代码执行完),event loop开始工作
  • 轮询查找callback Queue,如有则移动到call stack中执行
  • 然后继续轮询查找(永动机一样)
    在这里插入图片描述

2.DOM事件和event loop

  • JS是单线程的
  • 异步(AJAX,setTimeout等),使用回调,基于event loop
  • DOM事件也使用回调,基于event loop

3.Promise

  • 三种状态
  • 状态的表现和变化
  • then和catch对状态的影响
三种状态
  • pending resolved rejected
  • pending->resolved 或 pengding->rejected
  • 变化不可逆
const p1 = new Promise((resolve, reject) => {})
console.log(p1) //Promise {<pending>}

const p2 = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve()
  })
})
console.log('p2', p2) //pengding 一开始打印
setTimeout(() => {
  console.log('p2-settimeout', p2) //fulfilled
})

const p3 = new Promise((resolve, reject) => {
  setTimeout(() => {
    reject()
  })
})
console.log('p3', p3) //pengding 一开始打印
setTimeout(() => {
  console.log('p3-settimeout', p3) //rejected
})
状态的表现
  • pengding状态,不会触发then和catch
  • resolved状态,会触发后续的then回调函数
  • rejected状态,会触发后续的catch回调函数
const p1 = Promise.resolve(100)
p1.then((data) => {
  console.log('data1', data) //data1 100
}).catch((err) => {
  console.log(err)
})

const p2 = Promise.reject('err')
p2.then((data) => {
  console.log('data2', data)
}).catch((err) => {
  console.log('err2', err) //err2 err
})
then 和 catch改变状态
  • then 正常返回resolved,里面有报错则返回rejected
  • catch 正常返回resolved,里面有报错则返回rejected
//  then 正常返回resolved,里面有报错则返回rejected
const p1 = Promise.resolve().then(() => {
  return 100
})
console.log(p1) //fulfilled
//resolved 后续触发then回调
p1.then(() => {
  console.log(123) //123
})

const p2 = Promise.resolve().then(() => {
  throw new Error('then error')
})
console.log(p2) //rejected
//rejected后续触发catch回调
p2.then(() => {
  console.log(123)
}).catch((err) => {
  console.log('catch error', err) //catch error Error: then error
})

// catch 正常返回resolved,里面有报错则返回rejected
const p3 = Promise.reject('my error').catch((err) => {
  console.log(err) //my error
})
console.log(p3) //fulfilled
p3.then(() => {
  console.log(100) //100
})

const p4 = Promise.reject('my error2').catch((err) => {
  throw new Error('error')
})
console.log(p4) //rejected
p4.then(() => {
  console.log(200) //不执行
}).catch((err) => {
  console.log('p4 error', err) //p4 error Error: error
})
Promise总结
  • 三中状态
  • then和catch对状态的影响(重要)
  • then和catch的链式调用(常考)

4.async/await

  • 异步回调 callback hell
  • Promise then catch 链式调用,但也是基于回调函数
  • async/await是同步语法,彻底消灭回调函数
//async await
const src1 = 'http://www.imooc.com/static/img/index/logo_new.png'
const src2 = 'https://avatars3.githubusercontent.com/u/9583120'
function loadImg(src) {
  const p = new Promise((resolve, reject) => {
    const img = document.createElement('img')
    img.onload = () => {
      resolve(img)
    }
    img.onerror = () => {
      const err = new Error(`图片加载失败 ${src}`)
      reject(err)
    }
    img.src = src
  })
  return p
}

;(async function () {
  const img1 = await loadImg(src1)
  console.log(img1.width)
  const img2 = await loadImg(src2)
  console.log(img2.width)
})()
async/await和Promise的关系
  • async/await是消灭异步回调的终极武器
  • 但和Promise并不互斥
  • 反而,两者相辅相成
  • 执行async函数,返回的是Promise对象
  • await相当于Promise的then
  • try…catch…可捕获异常,代替了Promise的catch
//执行async函数,返回的是Promise对象
async function fn1() {
  return 100
}
const res1 = fn1() //执行async函数,返回的是一个promise对象
console.log('res1', res1) //res1 Promise {<fulfilled>: 100}
res1.then((data) => {
  console.log(data) //100
})
 // await相当于Promise的then
;(async function () {
  const p1 = Promise.resolve(100)
  const data = await p1
  console.log(data) //100
})()
;(async function () {
  const data2 = await 200 //相当于await Promise.resolve(200)
  console.log(data2) //200
})()
//  try...catch...可捕获异常,代替了Promise的catch
;(async function () {
  const p3 = Promise.reject('my error')
  try {
    const data = await p3
    console.log(data) //不执行
  } catch (error) {
    console.log(error) //my error
  }
})()
//  try...catch...可捕获异常,代替了Promise的catch
;(async function () {
  const p4 = Promise.reject('error')
  const data = await p4
  console.log(data) //不会执行
})()
异步的本质
  • async/await是消灭异步回调的终极武器
  • JS还是单线程,还得是有异步,还得是基于event loop
  • async/await只是一个语法糖
async function async1() {
  console.log('async1 start') //2
  //await 后面的内容,都可以看做callback里的内容,即异步
  //4同步代码执行完了才执行5 的异步代码
  await async2()
  console.log('async1 end') //5 关键在这一步,它相当于放在 callback 中,最后执行
}

async function async2() {
  console.log('async2') //3
}

console.log('script start') //1
async1()
console.log('script end') //4
async function async1() {
  console.log('async1 start') //2
  await async2()
  console.log('async1 end 1') // 5          
  await async3()
  console.log('async1 end 2') //7
}

async function async2() {
  console.log('async2') //3
}

async function async3() {
  console.log('async3') //6
}

console.log('script start') //1
async1()
console.log('script end') //4
for … of
  • for … in (以及forEach for)是常规的同步遍历
  • for … of常用于异步的遍历
function multi(i) {
  const p = new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve(i * i)
    }, 1000)
  })
  return p
}

const nums = [1, 2, 3]

nums.forEach(async (i) => {
  const res = await multi(i)
  console.log(res) //1秒后,1,4,9同时大打印出来
})
;(async function () {
  for (let k of nums) {
    const res = await multi(k)
    console.log(res)
  }
})() //1,4,9间隔一秒分别打印

5.宏任务macroTask和微任务microTask

  • 什么是宏任务,什么是微任务
  • eventloop 和 DOM 渲染
  • 微任务和宏任务的区别
微任务和宏任务
  • 宏任务:setTimeout,setInterval,AJAX,DOM事件
  • 微任务:Promise async/await
  • 微任务执行时机比宏任务要早
console.log(100) //1
// 宏任务
setTimeout(() => {
  console.log(200)  //4
})
// 微任务
Promise.resolve('100').then(() => {
  console.log(300) //3
})
console.log(400) //2
event loop和DOM渲染
  • 再次回归一遍event loop的过程
  • JS是单线程的,而且和DOM渲染共用一个线程
  • JS执行的时候,得留一些时机供DOM渲染

加入DOM渲染的event loop

  1. 每次Call Stack清空(即每次轮询结束),即同步任务完成
  2. 都是DOM重新渲染的机会,DOM结构如有改变则重新渲染
  3. 然后再去触发下一次event loop
const $p1 = $('<p>一段文字</p>')
const $p2 = $('<p>一段文字</p>')
const $p3 = $('<p>一段文字</p>')
$('#container').append($p1).append($p2).append($p3)

console.log('length', $('#container').children().length)
alert('本次 call stack 结束,DOM 结构已更新,但尚未触发渲染')
微任务和宏任务的区别
  • 宏任务: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}`)
})
从event loop解释,为何微任务执行更早
  • 微任务是ES6语法规范的
  • 宏任务是由浏览器规定的
  • 微任务会放在单独的micro task queue中

完整event loop执行过程

  1. call stack清空
  2. 执行当前的微任务
  3. 尝试DOM渲染
  4. 触发event loop
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值