JS中的事件循环机制与异步处理
JavaScript是单线程的,这意味着它一次只能执行一个任务。为了处理异步操作,JavaScript依赖于事件循环(Event Loop)机制。事件循环允许JavaScript在等待异步操作完成时继续执行其他任务,从而实现非阻塞的异步编程。
1.事件循环机制(Event Loop)
事件循环机制依靠调用栈和任务队列完成事件循环,从而实现JS的异步编程。
调用栈(Call Stack): 都知道栈是一种后进先出的数据结构,它用于跟踪正在执行的函数。当一个函数被调用则进栈,函数执行完成后则出栈。
任务队列(Task Queue):
任务队列是一种先进先出的数据结构,它用于存放等待执行的异步函数。
事件循环(Event Loop):
事件循环不断地检查调用栈是否为空,如果调用栈为空,并且任务队列中有待处理的任务,事件循环会将任务队列中的第一个任务移到调用栈中执行。
1.同步任务与异步任务
在js中任务被分为同步与异步任务,同步任务将在执行流程中直接进入调用栈进行执行,而异步任务会先进入任务队列等待执行。
同步任务: 在主线程上排队执行的任务,只有前一个任务执行完毕,才能执行后一个任务,例如console.log()。
异步任务:不进入主线程、而进入”任务队列”的任务。
2.微任务与宏任务
在JavaScript中,微任务(microtasks)和宏任务(macrotasks) 是事件循环中处理异步任务的两种类型。它们代表了不同优先级的任务队列,事件循环会按照特定的顺序执行这些任务。 宏任务主要由(浏览器、Node)发起,微任务由js引擎发起。
微任务:nextTick(),Promise.then()、Promise.catch(),async/await宏任务:setTimeout,setInterval,I/O,script代码快
3.执行过程
在了解事件循环机制后,事件循环执行流程为同步任务进栈执行,微任务进栈执行,宏任务进栈执行。下面用代码说明:
console.log(1); //同步任务
setTimeout(function consoleLog2(){
console.log(2); //回调 宏任务
},0)
const p = new Promise((resolve)=>{ //promise为同步任务
console.log(3);
resolve(100);
console.log(4);
})
p.then(data=>{
console.log(data); //微任务
})
console.log(5) //同步任务
以上代码在事件循环中的流程为:
console.log(1)进栈执行➡️console.log(2)进入任务队列➡️console.log(3)进栈执行➡️resolve,console.log(data)进入微任务队列➡️console.log(4)进栈执行➡️console.log(5)进栈执行➡️微任务console.log(data)出队并进栈执行➡️宏任务console.log(2)出队进栈执行
运行结果为:
1
3
4
5
100
2
2.异步操作
了解完上面的内容后,可以开始了解js中三种异步操作了: 异步回调、Promise、async/await
1.异步回调
先看以下代码:
A(_function) {
_function()
console.log("A")
}
B(){
for (let i = 0;;i++){
console.log(i)
if (i===9999){
console.log("B")
break
}
}
}
//执行
A(B)
//结果B A
使用函数B()模拟耗时函数,通过运行结果可以看到,当打印到9999时才会一次输出B A,显然A函数想要执行必须等待B函数执行完毕,函数的执行依然是自上而下顺序执行,因为函数A与B都为同步函数,此为同步调用。 要想B的执行不阻塞A,只需要在A里进行异步处理,代码如下:
A(_function) {
setTimeout(_function,0)
console.log("A")
}
//执行
A(B)
//结果A B
其实即使我们将setTimeout的延时改为1000,结果也会是A B,因为setTimeout()函数支持异步处理,它在循环机制中属于宏任务,它的执行不会影响主线程中的同步任务,因此函数A不会因为B的执行而发生阻塞。但如果回调层数太多,代码耦合度高,难以维护,且会造成回调地狱。
2.Promise
Promise是异步编程的另一种解决方案,它是一个对象,可以获取异步操作的消息,他的出现大大改善了异步编程的困境,避免了传统的解决方案回调函数的回调地狱。
所谓Promise,简单说就是一个容器,里面保存着某个未来才会结束的事件(通常是一个异步操作)的结果。从语法上说,Promise 是一个对象,从它可以获取异步操作的消息。Promise 提供统一的 API,各种异步操作都可以用同样的方法进行处理。
Promise有三种状态:Pending(进行)),Resolved(完成),Rejected(拒绝)。状态的转变依靠resolve()和reject()函数来实现。
Promise构造函数接收一个函数作为参数,我们需要处理的异步任务就在该函数体内,该函数的两个参数是resolve,reject。异步任务执行成功时调用resolve函数返回结果,反之调用reject。
Promise对象的then方法用来接收处理成功时响应的数据,catch方法用来接收处理失败时相应的数据。
const promise = new Promise(function(resolve, reject) {
// 异步处理
if (/* 异步操作成功 */){
resolve(value);
} else {
reject(error);
}
});
1.Promise方法
Promise有两个常用的方法:then()、catch()。
1.then()
当Promise执行的内容符合成功条件时,调用resolve函数,失败就调用reject函数。
promise.then(function(value) {
// success
}, function(error) {
// failure
});
2.catch()
该方法相当于then方法的第二个参数,指向reject的回调函数。但它可以在执行resolve回调函数时,如果出现错误,抛出异常,不会停止运行,而是进入catch方法中。
另外还有all()、race()、finally,可以前往此链接查阅学习。
3.async/await
Promise异步编程虽然解决了回调带来的回调地狱问题,但当需要执行多个异步操作时,Promise带来了大量.tnen代码,async/await出现的原因便是减少代码量,并看起来更加简洁优雅。当需要处理由多个Promise组成的then链的时候,async/await这种优势就能体现出来了。
takeTimeDelay(n) {
return new Promise(resolve => {
setTimeout(() => resolve(n + 1), 1000);
});
}
step1(n) {
console.log(`step1 = ${n}`);
return this.takeTimeDelay(n);
}
step2(n) {
console.log(`step2 = ${n}`);
return this.takeTimeDelay(n);
}
step3(n) {
console.log(`step3 = ${n}`);
return this.takeTimeDelay(n);
}
// Promise实现
_promise() {
const n = 0;
this.step1(n)
.then(time2 => this.step2(time2))
.then(time3 => this.step3(time3))
.then(result => {
console.log(`result is ${result}`);
});
}
// async实现
async _async() {
const n = 0
const value1 =await this.step1(n)
const value2 =await this.step2(value1)
const result =await this.step3(value2)
console.log(`result is ${result}`);
}
//结果
/*
step1 = 0
step1 = 1
step1 = 2
result is 3
*/
结果两者是一样的,但是利用async/await实现显然要清晰方便很多,看上去几乎跟同步代码一样。
async定义一个函数为异步函数,如果async函数中有返回值,内部会调用Promise.resolve()方法把它转化成一个promise对象作为返回,若函数没有返回值,则返回undefined。 await关键字会暂停当前的异步函数,等待异步操作完成,但不会阻塞外部代码,不会阻塞事件循环。
//定义asyncFunction
async asyncFunction() {
return '1'
}
//执行
asyncFunction().then(val => {
console.log(val)
})
console.log('2')
//结果
2
1
因为async函数返回的是Promise对象,因此以下两种代码方式等效:
// 方法1
function _function() {
return Promise.resolve('Hello');
}
// 方法2
async function _function() {
return 'Hello';
}
想要获取函数的执行结果,就要使用.then()或者.catch()注册回调函数,因此以上两个函数要获取结果的代码为:
this._function1().then(res => {
console.log(res)
})
async会定义一个函数为异步函数,它若有返回值,将会被封装在Promise对象中返回,如果对返回值通过.then()、.catch()注册了回调函数,该回调会进入任务队列中(微任务)等待同步任务完成才会被调用。
学习过程中参考了: JS中的一部编程、JavaScript代码执行顺序