JS异步

97 阅读4分钟

异步和单线程

单线程

  • JS是单线程语言,只能同时做一件事
  • 浏览器和nodejs已支持启动进程,如Web Worker
  • JS和DOM渲染共用一个线程,因此JS可修改DOM结构

异步

  • 遇到等待(网络请求、定时任务)不能卡住
  • 需要异步
  • 回调callback函数形式

同步和异步

  • 基于JS是单线程语言
  • 异步不会阻塞代码执行,同步会

手写promise加载图片

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
}
loadImg(url1).then(img1 => {
  console.log(img1.width);
  return img1
}).then(img1 => {
  console.log(img1.height);
  return loadImg(url2)
}).then(img2 => {
  console.log(img2.width);
  return img2
}).then(img2 => {
  console.log(img2.height);
})

setTimeout笔试题

// setTimeout 笔试题
console.log(1)
setTimeout(function () {
    console.log(2)
}, 1000)
console.log(3)
setTimeout(function () {
    console.log(4)
}, 0)
console.log(5)
// 答案:1 3 5 4 2

异步进阶

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

什么是event loop

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

JS如何执行

  • 从前到后,一行一行执行
  • 如果某一行报错,则停止执行下面的代码
  • 先把同步代码执行完,再执行异步代码

总结event loop过程

  • 同步代码,一行一行放在call stack执行
  • 遇到异步(定时,网络请求),会先“记录”下,等待时机
  • 时机到了,就移动到callback queue
  • 如果call stack为空了,即同步代码执行完毕,event loop开始工作
  • 轮询查找callback queue,如有,移动到call stack执行
  • 然后继续轮询查找

DOM事件和event loop

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

peomise进阶

三种状态

  • pending
  • fulfilled
  • rejected

状态的变化和表现

  • pending -> resolved,pending -> rejected
  • 变化是不可逆的
  • pending状态下,不会触发then和catch
  • resolved状态下,会触发then
  • rejected状态下,会触发catch

then和catch对状态的影响

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

关于then和catch的面试题

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

// 第二题
Promise.resolve().then(() => { // 返回 rejected 状态的 promise
    console.log(1)
    throw new Error('erro1')
}).catch(() => { // 返回 resolved 状态的 promise
    console.log(2)
}).then(() => {
    console.log(3)
})
// 答案:1 2 3

// 第三题
Promise.resolve().then(() => { // 返回 rejected 状态的 promise
    console.log(1)
    throw new Error('erro1')
}).catch(() => { // 返回 resolved 状态的 promise
    console.log(2)
}).catch(() => {
    console.log(3)
})
// 答案:1 2

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/await是语法糖,本质上还是回调函数

async function async1 () {
  console.log('async1 start') // 2
  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
// 即,只要遇到了 `await` ,后面的代码都相当于放在 callback 里。

微任务/宏任务

定义

  • 宏任务:setTimeout,setInterval,Ajax,DOM事件
  • 微任务:Promise,async/await
  • 微任务执行时机比宏任务早

event loop和DOM渲染

  • JS是单线程的,和DOM渲染共用一个线程
  • JS执行的时候,要留一些时机供DOM渲染

执行过程

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

微任务和宏任务

  • 区别:宏任务在DOM渲染之后触发;微任务在DOM渲染之前触发
  • 根本区别:微任务是es6语法规定的,由JS引擎统一处理;宏任务是由浏览器规定的,由浏览器(nodejs)干预处理

JS异步面试题

执行顺序

async function async1() {
  console.log("async1 start") // 2
  await async2()
  // await后面的都作为回调内容 - 微任务
  console.log("async1 end") // 6
}

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

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

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

async1()

// 初始化promise时,传入的函数会立即被执行
new Promise(function (resolve) {
  console.log("promise1") // 4
  resolve()
}).then(function () { // 微任务
  console.log("promise2") // 7
})

console.log("script end") // 5