AJAX是异步的,但async-await为何看起来像是同步的?浅析JS的EventLoop

前言

前端开发中常常使用async-await来替换.then() .catch()的写法,我们也知道只有await到了想要的值才会接着往下执行。async-await允许你以一种更同步的方式 编写异步代码

同步就同步,异步就异步,"以一种更同步的方式编写异步代码"是什么玩意儿?不扯淡呢嘛

为了更好的理解async-await的作用,就不得不先来了解一下JS的事件循环模型:

JS 是单线程的,主线程中的任务在调用栈中从上至下依次执行(同步)。但是为了防止线程阻塞(比如遇到定时器),会借助浏览器实现异步 的效果。当异步条件满足之后(如计时时间到了),会将任务放入任务队列中,等主线程内的东西执行完了,再挨个取出来执行。但是,异步任务的任务队列又可以分为宏任务队列和微任务队列。微任务队列中的任务会比宏任务队列中的任务先执行

宏任务和微任务:

宏任务: setTimeout setInterval 等属于宏任务, 上一个宏任务执行完, 才会考虑执行下一个宏任务

微任务: promise .then .catch的需要执行的内容, 属于微任务, 满足条件的微任务, 会比宏任务先执行(也可以理解成插在宏任务队列的最前面执行)

再次强调,调用栈(主线程)中的代码从上至下执行,遇到异步时又分为宏任务和微任务

事件循环队列:

直接看下面这段代码:

javascript 复制代码
console.log(1)	// 主线程任务

setTimeout(function() {
  console.log(2) // 宏任务1
}, 0)

const p = new Promise((resolve, reject) => {
  resolve(1000)	// 主线程任务
})
p.then(data => {
  console.log(data)  // 微任务
})

console.log(3)	// 主线程任务

// 1 3 1000 2

对于这道题,首先有个坑。new Promise 这行代码属于主线程的任务,只不过它把 promise 状态从 pedding 设置成 fullfield ,而不是打印

p.then 就是微任务,所以它会进入微任务循环队列,会在宏任务队列之前执行

async-await 的认知:

在前端开发中,常见场景是在 AJAX 请求时,我们会有诸如下面这种写法:

javascript 复制代码
const getData = async () => {
  const res = await axios.get('xxx')
  console.log(res)
}

getData()

async/await是建立在Promise对象基础上的语法糖,它允许你以一种更同步的方式 编写异步代码。一个async函数返回一个Promise对象,而await关键字则用于等待 一个Promise的结果

说白了就是借助 async-await,让我们的这段代码看起来更像是:代码走到第二行,会等 axios 返回结果给 res 了再往下执行。这听起来更像是同步的表述,可 AJAX 我们都知道它是异步的,难道冲突了?

先假设我们不使用 async-await 我们会怎么写?我们会写成.then .catch 的形式:

typescript 复制代码
axios.get('xxx')
  .then((data)=>{
    const res = data
    console.log(res)
  })
  .catch(()=>{
    xxx
  })

这段代码就好理解了,axios.get 是调用栈(主线程)任务,then() catch() 回调是微任务

观察上述两段代码,你会发现,await等待 Promise的结果之后 执行的内容其实就是.then 里面的回调。换个说法,await 拿到结果 之后执行的是微任务,而请求结果的过程(也就是)axios.get('xxx')仍然是主线程就执行的任务

注意,此处一直在强调是拿到结果之后

搞明白了这一点,其实前面那个看似冲突的问题也就解决了

javascript 复制代码
const getData = async () => {
  const res = await axios.get('xxx')
  console.log(res)
}

getData()

首先执行getData(),然后发现有个axios.get('xxx')主线程任务,所以执行它;而等待返回结果、console.log(res)会放入微任务队列;等主线程执行完毕,再来执行它

所以async-await看起来像同步,是因为在执行完主线程后执行微任务

几道题目:

  1. 例题一
scss 复制代码
async function fn () {
  console.log(111)
}
fn()
console.log(222)
// 111 222

不要一看见 async 就觉得有异步,像这里它都没出现 await,所以其实和普通函数一样

  1. 例题二
javascript 复制代码
async function fn () {
  console.log('fn start')
  const res = await fn2()
  console.log(res)  // 微任务
}
async function fn2 () {
  console.log('fn2 start')
}
fn()
console.log('test')

// fn start -> fn2 start -> test -> undefined

走到第九行,开始执行 fn() 的内容;打印fn start;往下发现有个 fn2(),执行 fn2() 的内容;后面等待 fn2() 返回结果,以及 console.log(res) 属于微任务,进入微任务队列;此时第九行代码执行完毕;但调用栈任务还没完,还有一个第十行,所以打印 test;打印完 test,页面栈任务算是执行完毕;去任务队列找有没有要执行的东西,发现微任务队列还有个 console.log(res)没执行,但由于fn2()并没有返回东西,所以打印 undefined

  1. 例题三
javascript 复制代码
async function async1() {
	console.log('async1 start')
	await async2()
	console.log('asnyc1 end')
}

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

console.log('script start')

setTimeout(() => {
	console.log('setTimeOut')
}, 0)

async1()

new Promise(function (reslove) {
	console.log('promise1')
	reslove();
}).then(function () {
	console.log('promise2')
})

console.log('script end')

// script start
// async1 start
// async2
// promise1
// script end
// asnyc1 end
// promise2
// setTimeOut

思路:先执行调用栈任务 -> 执行完毕看看微任务队列有没有任务 -> 执行完看看宏任务队列有没有任务

走到第 9 行,打印script start;第十行是异步,交给浏览器,之后满足条件进入 任务队列;走到第 13 行,进入 async1();打印async1 start;走到第 3 行,进入 async2(),打印async2;然后等待 async2() 的结果和打印asnyc1 end进入 任务队列;自此执行完了第 13 行的代码;接着往下走;到第 15 行打印promise1then() 回调进入 任务队列;接着打印script end;自此调用栈任务全部执行完毕

由于微任务有两个任务,按照先后顺序,依次打印asnyc1 endpromise2

微任务也执行完了,从宏任务队列拿任务执行,也就是打印setTimeOut

相关推荐
程序员海军12 分钟前
2024 Nuxt3 年度生态总结
前端·nuxt.js
m0_7482567822 分钟前
SpringBoot 依赖之Spring Web
前端·spring boot·spring
web135085886351 小时前
前端node.js
前端·node.js·vim
m0_512744641 小时前
极客大挑战2024-web-wp(详细)
android·前端
若川1 小时前
Taro 源码揭秘:10. Taro 到底是怎样转换成小程序文件的?
前端·javascript·react.js
潜意识起点1 小时前
精通 CSS 阴影效果:从基础到高级应用
前端·css
奋斗吧程序媛1 小时前
删除VSCode上 origin/分支名,但GitLab上实际上不存在的分支
前端·vscode
IT女孩儿1 小时前
JavaScript--WebAPI查缺补漏(二)
开发语言·前端·javascript·html·ecmascript
m0_748256564 小时前
如何解决前端发送数据到后端为空的问题
前端
请叫我飞哥@4 小时前
HTML5适配手机
前端·html·html5