无畏异步!详解Javascript任务循环、宏任务、微任务、Promise、async/await

一、简介

关于JavaScript的同步异步,首先要明确的一个点,那就是js是单线程的,也就是说,从极小的时间尺度上来说,js同一时间只能做一件事。而事件循环则是js异步的核心机制,事件循环简单来说也就是处理宏任务,微任务的一个流程机制,而Promiseasync/await则也能简单理解为创建宏任务微任务的js语句

二、事件循环

首先简单的了解一下js的事件循环模型,事件循环模型。在js中的代码可以分为两种:同步代码和异步代码。而异步代码又可以分为微任务的异步代码和宏任务的异步代码。在js的事件循环的内存模型中,有三种数据结构,执行栈/回调栈(执行同步代码)、宏任务队列(存储宏任务)、微任务队列(存储微任务) ,以上内容可以看下图。

js代码分类
事件循环
事件循环

从上图其实可以看出,宏任务代码会被放进宏任务队列,微任务代码则会被放进微任务队列,它们会被按一定规则顺序出队并入栈到执行栈中,让主线程执行。

三、宏任务和微任务

1. 宏任务

宏任务是指由宿主环境,如浏览器,node.js发起的一些任务,例如事件处理、setTimeoutsetInterval 等。这些任务会被放入宏任务队列中,等待执行。在事件循环中,宏任务队列的任务会在执行栈为空时被依次执行,直到队列为空setTimeout 设置的回调函数就是一个典型的宏任务。尤其需要注意的是:整个js代码本身就是一个宏任务,会被最先加入执行栈

2.微任务

微任务是指由js引擎发起的任务,最典型的就是 Promise.then() 或 Promise.catch()注册的回调函数 (Promise本身是同步的,是promise的then和catch回调为微任务异步代码)这些任务会被放入微任务队列中,在当前宏任务执行完毕后立即执行,优先于下一个宏任务。在事件循环中,微任务队列的任务会在当前宏任务执行完毕后立即执行,直到队列为空。Promise 的 then 和 catch 回调函数就是典型的微任务。

3.Promise

在对宏任务微任务举例前,先了解一下js的Promise对象。
Promise意为"承诺",Promise对象承诺,在未来的某个时刻将会返回一个结果,而我们想要在Promise在返回结果时处理结果,就可以调用其then和catch的方法回调来异步处理。举个例子,这里创建了一个Promise对象,该Promise内部发起了一个setTimeout的任务(宏任务),该任务会在2s后打印‘Ok’,并告诉Promise可以调用then或catch回调。

Promise 有三种状态,分别是:
Pending(进行中):Promise 对象刚被创建时的初始状态,表示操作尚未完成。
Fulfilled(已成功):Promise 对象的操作成功完成时的状态,此时会调用 resolve 方法。
Rejected(已失败):Promise 对象的操作失败时的状态,此时会调用 reject 方法。
在 Promise 对象的生命周期中,它会从 Pending 状态转变为 Fulfilled 或 Rejected 状态,一旦转变为其中一种状态后,就不会再改变。当 Promise 处于 Fulfilled 状态时,会执行 then 方法中的成功回调函数;当处于 Rejected 状态时,会执行 catch 方法中的失败回调函数。

//创建promise对象
//传入回调函数,回调函数接受两个参数,resolve和reject
//rsolve和reject都是函数,都能通知promise返回结果,但resolve会调用then而reject会调用catch回调
new Promise((resolve, reject) => {
	//设置2s后打印“ok"并通知promise处理
    setTimeout(() => {
        console.log('ok')
        resolve(200)
    }, 2000)
    //打印返回的结果200
}).then(res => console.log(res)).catch(e => {});
//打印56
console.log(56)

以上代码的打印顺序为56 -> ok -> 200。
我们利用之前提到的任务循环、宏任务、微任务来简要分析一下。

首先之前提到过,整个js代码就是一个宏任务,所以我们将 最先执行整个js同步代码
在这里插入图片描述

我们再来看个例子,看看以下代码的打印顺序。

console.log(1)

new Promise(res => {

    console.log(2)
    
    setTimeout(() => {
        console.log(3)
        res(200)
    })
    
    console.log(4)
    
}).then(v => console.log(v))

console.log(5)

只需要简单记住一句话,同步代码优先执行,遇到宏任务异步代码就放进宏任务队列排队,遇到微任务代码也同样放进微任务队列排队,都得等到同步代码执行完毕后再开始执行。上面代码的打印顺序为1 -> 2 -> 4 -> 5 -> 3 -> 200,我们对其简单的分析以下。

console.log(1)	// 1.同步代码,优先执行,打印1
new Promise(res => {
    console.log(2)	// 2.同步代码,优先执行,打印2
    setTimeout(() => { //3.放入宏任务队列
        console.log(3)
        res(200)
    })
    console.log(4)	//4. 同步代码,打印4
}).then(v => console.log(v)) //5.then/catch,放入微任务队列
console.log(5) //5.同步代码打印5
//6.同步代码执行结束,优先查看微任务,因为promise还没有得到结果,无法执行微任务,所以查看宏任务
//7.执行宏任务队列,即setTimeout,于是打印3,执行res(200)。
//8、最后执行微任务队列里的任务,即then,打印res,即200

总结一下,所谓的任务循环简单来说其实就是 同步代码、宏任务、微任务被主线程循环执行主线程在执行栈执行同步代码时,会遇到异步代码,并将其按照宏任务、微任务的分类放入指定的任务队列排队,当执行栈为空时,优先查看微任务的任务队列,把能够执行的微任务放入执行栈中执行,而在执行过程中,可能又会遇到异步代码,于是又会将其放入相应的任务队列,宏任务亦是如此,就这样循环往复,形成了js任务队列。

四、async/await

很多人在初次接触这两关键词时都会一头雾水,asyncawait 是 JavaScript 中用于处理异步操作的关键字。通过使用 async 关键字定义的函数会返回一个 Promise 对象,而在这个函数中可以使用 await 关键字来等待 Promise 对象的解决(resolve)。当遇到 await 关键字时,函数会暂停执行直到这个 Promise 对象被解决,然后继续执行下面的代码
这种方式使得异步操作更加直观和易于理解,避免了回调地狱的情况。通常 await 关键字用于等待异步函数的返回结果,从而使得代码看起来更加像同步代码的写法。
简单来说,asyncawait是一种语法糖, 让异步操作更加易于管理和理解,简化了异步编程的方式。

1.async

async用于定义一个异步函数,用async定义的函数会自动返回一个promise对象(如果在async函数中返回非Promise对象也会被自动包装成promise)。

//定义异步函数
async function test() {
	//返回10
    return 10;
}
//调用test,最后打印发现10被包装成了Promise
console.log(test()) //Promise

而上面的代码和下面这段代码其实是等价的。

function test() {
	//手动返回Promise
    return new Promise(res => res(10))
}
console.log(test())

2.await

await 是 JavaScript 中用于等待异步操作完成的关键字。await 只能在 async 函数内部使用,用于等待一个 Promise 对象的状态变为 resolved后继续执行下面的代码。
当在 async 函数中使用 await 关键字时,Js引擎会暂停函数的执行,直到 await 后面的 Promise 对象被解决。一旦 Promise 对象被解决,await 将返回 Promise 对象的解决值,然后函数继续执行下面的代码。

举个例子

//创建一个函数,返回promise对象
function test() {
    return new Promise(res => {
        console.log(6)
        setTimeout(() => {
            console.log(1)
            res('ok')
        }, 2000)
    })
}

async function excute() {
    console.log(2)
    let res = await test()
    console.log(3)
}

console.log(4)
excute()
console.log(5)

以上代码的打印结果为:4 -> 2 -> 6 -> 5 -> 1 -> 3。接下来让我们来仔细分析一下。

我们首先要明确的一个点就是,当使用了await关键字之后,从await往下直到右大括号的代码都是微任务,会被加入微任务队列。

function test() {
    return new Promise(res => {
        console.log(6) //5.打印6,(再次强调,promise本身是同步的)
        setTimeout(() => {  //6.加入宏任务
            console.log(1) //9.由于promise为得到结果,
            				//微任务队列中任务无法执行,所以转而执行宏任务,打印1
            res('ok') //10.设置promise状态为成功
        }, 2000)
    })
}

async function excute() {
    console.log(2) //3.打印2
    let res = await test() //4.进入执行
    console.log(3) //7.await之后直到右大括号的代码,属于微任务异步代码,加入微任务				//11.promise拿到了结果,可以执行微任务队列中的任务了,打印3
}

console.log(4) //1.同步代码,打印4
excute()  //2.进入函数执行
console.log(5) //8.同步代码,打印5

其实以上的代码可以等价于下面这段代码。

function test() {
    return new Promise(res => {
        console.log(6) //3.同步代码执行
        setTimeout(() => { //4.加入宏任务
            console.log(1) //7.执行宏任务队列任务
            res('ok')
        }, 2000)
    })
}




console.log(4) //1.同步代码执行
console.log(2) //2.同步代码执行
test().then(res => {  //5.then 加入微任务
    console.log(3)  //8.执行微任务队列任务
})
console.log(5) // 6.同步代码执行

再举个例子练习一下,你可以拿出一张纸跟着模拟一下过程。

function test() {
    console.log(1)
    return new Promise(res => {
        console.log(0)
        setTimeout(() => {
            console.log(2)
            res('ok')
            console.log(3)
        })
        console.log(10)
    })
}

async function excute() {
    console.log(5)
    let res = await test()
    console.log(6)
    console.log(res)
}

console.log(7)
excute()
console.log(8)

最后打印的结果为:7 -> 5 -> 1 -> 0 -> 10 -> 8 -> 2 -> 3 -> 6 -> ok
如果你能轻松写出正确的打印顺序,那么恭喜你,js异步对你而言已是信手拈来!

五、总结

Js 中的任务循环是管理异步操作的机制。在任务循环中,有两种任务类型:宏任务和微任务。
宏任务包括整体代码块、setTimeoutsetInterval、I/O 操作等,它们会被放入到宏任务队列中等待执行。
微任务包括 Promise、process.nextTick、MutationObserver 等,它们会被放入到微任务队列中,在每个宏任务执行完毕后立即执行。
Promise 是 JavaScript 中用于处理异步操作的一种方式,它的状态包括 pending(进行中)、fulfilled(成功)和 rejected(失败),可以通过 then 方法来处理成功和失败的情况。
async/await 是 ES8 引入的语法糖,用于更简洁地处理异步操作。通过在函数前面加上 async 关键字,函数会返回一个 Promise 对象;而在异步操作前加上 await 关键字,可以等待该异步操作完成,使得代码看起来更像同步执行。
在任务循环中,Promise 的回调函数会被放入微任务队列,而 async/await 实际上是基于 Promise 的封装,也会被放入微任务队列中。微任务优先级高于宏任务,因此微任务会在宏任务执行之前执行
通过合理地利用宏任务和微任务,结合 Promise 和 async/await,可以更加高效地处理 JavaScript 中的异步操作,提高代码的可读性和可维护性。

  • 15
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值