事件循环(event loop)
- JS分为同步任务和异步任务,同步任务会在主线程上执行(形成执行栈,先进后出),异步任务会先放置在任务队列中(先进先出);
- 当主线程上的同步任务全部执行完成后,js会在任务队列中依次取出异步任务并执行。
- JS主线程不断的循环往复的从任务队列中读取任务,执行任务,这中运行机制遍称为事件循环。
宏任务(Macrotasks)与微任务(Microtasks) 【javascript|事件循环、宏任务与微任务、Promise与 Async/Await以及常见面试题】异步任务可分为 宏任务 和 微任务两类,不同的API注册的异步任务会依次进入自身对应的队列中,然后等待 Event Loop 将它们依次压入执行栈中执行。从本质上来说,宏任务是ES6语法规定的,微任务是浏览器规定的,微任务的优先级要高于宏任务。
宏任务包含的API有:script(整体代码)、setTimeout、setInterval等
微任务包含的API有:Promise等
因此一次事件循环的大致过程为:
------------------------------------------
文章图片
Promise 与Async/Await Async/Await可看作是Promise异步执行的优化,在其之前,Promise需要多个then()来链式调用,而有了await,就不必这么繁琐。
从定义上来看, async 用于申明一个 function 是异步的,而 await 用于等待一个异步方法执行完成。
Promise中的异步体现在then和catch中,所以写在Promise中的代码是被当做同步任务立即执行的。而在async/await中,在出现await出现之前,其中的代码也是立即执行的。那么出现了await时候发生了什么呢?
其实,Async函数会返回一个Promise对象,Await用于等待函数的返回值,这个返回值可以是一个Promise对象,也可以是任意一个表达式的结果。
- 如果它等到的不是一个 Promise 对象,那 await 表达式的运算结果就是它等到的东西。
- 如果它等到的是一个 Promise 对象,await 会阻塞当前路径后面的代码,等着 Promise 对象 resolve,然后得到 resolve 的值,作为 await 表达式的运算结果。
其实,Promise 构造函数中的第一个参数,是在 new 的时候执行,构造函数执行时,里面的参数进入执行栈执行;而后续的 .then 则会被分发到 microtask 的 Promise 队列中去。同理,在async/await中,函数从上到下依次执行,当碰到返回值时,await
会等待函数返回值并且将其放置在对应的任务队列后让出执行栈,JS向下执行其余代码,之后await得到返回值,执行await语句。
练习题 学了这么多,接下来我们用几道题巩固一下知识。
练习一
function testSometing() {
console.log("执行testSometing");
return "testSometing";
}async function testAsync() {
console.log("执行testAsync");
return Promise.resolve("hello async");
}async function test() {
console.log("test start...");
const v1 = await testSometing();
//关键点1
console.log(v1);
const v2 = await testAsync();
console.log(v2);
console.log(v1, v2);
}test();
var promise = new Promise((resolve)=> {
console.log("promise start..");
resolve("promise");
});
//关键点2
promise.then((val)=> console.log(val));
console.log("test end...")
我们来分析一下这个代码的执行顺序:
- JS会从上到下执行script整体这一宏任务
- 先定义了一个testSomething()函数,然后定义了异步函数testAsync(),test()
- 执行test异步函数,打印“test start….”,此时出现await,会先打印“执行testSometing”,随后await等待返回值,并且让出执行栈。
- 函数接着向下执行,定义了一个promise异步函数,new的时候执行第一个参数,打印promise start…,随后Promise状态变为resolved
- 继续向下执行,promise.then()中的函数被放到微任务队列中
- 打印test end…
- 第一轮执行完毕,又会跳到await,此时函数返回testSometing,并打印
- 接着又遇到await testAsync(),打印“执行testAsync”,让出执行栈
- 此时,微队列中的任务promise.then()进入执行栈,打印promise
- 然后又跳到await处。得到返回值 hello async并打印
- 打印testSometing hello async
因此,整个函数的返回结果为
test start...
执行testSometing
promise start..
test end...
testSometing
执行testAsync
promise
hello async
testSometing hello async
练习二
console.log('script start');
setTimeout1(function() {
console.log('timeout1');
}, 10);
new Promise(resolve => {
console.log('promise1');
resolve();
setTimeout2(() => console.log('timeout2'), 10);
}).then(function() {
console.log('then1')
})console.log('script end');
执行过程:
- 执行宏任务script整体代码,打印script start
- 将setTimeout1放入宏任务队列中
- 遇到new,执行Promise,打印promise1,执行resolve(),Promise状态变为resolved,将.then()放入微任务队列中
- 将setTimeout2放入宏任务队列
- 打印script end
- 微任务队列中的任务进入执行栈,打印then1
- 随后微任务队列为空,这一事件循环结束
- 第二轮循环,执行宏任务队列中的setTimeout1,打印timeout1,没有微任务,结束循环
- 第三次循环,打印timeout2
因此,代码执行结果为
script start
promise1
script end
then1
timeout1
timeout2
练习三(之一)
async function async1() {
console.log('async1 start');
await async2();
console.log('async1 end');
}
async function async2() {
console.log('async2');
}console.log('script start');
setTimeout(function() {
console.log('setTimeout');
}, 0)async1();
new Promise(function(resolve) {
console.log('promise1');
resolve();
}).then(function() {
console.log('promise2');
});
console.log('script end');
代码执行过程:
- 定义异步函数async1 与async2
- 打印script start
- 将setTimeout放入宏任务队列中
- 调用async1,打印async1 start,遇到await,紧接着输出async2,然后将await后面的代码也就是console.log(‘async1 end’)加入到微任务队列中,接着跳出async1函数来执行后面的代码。
- 遇到new,执行Promise,打印promise1,执行resolve(),将.then()放入微任务队列中
- 打印script end
- 微任务队列中的任务依次进入执行栈,打印async1 end,promise2
- 检测到没有微任务,第一次事件循环结束
- 第二次事件循环,执行宏任务,打印setTimeout
因此,代码执行结果为
script start
async1 start
async2
promise1
script end
async1 end
promise2
setTimeout
练习三(之二)
async function async1() {
console.log('async1 start');
await async2();
console.log('async1 end');
}
async function async2() {
//async2做出如下更改:
new Promise(function(resolve) {
console.log('promise1');
resolve();
}).then(function() {
console.log('promise2');
});
}
console.log('script start');
setTimeout(function() {
console.log('setTimeout');
}, 0)
async1();
new Promise(function(resolve) {
console.log('promise3');
resolve();
}).then(function() {
console.log('promise4');
});
console.log('script end');
执行顺序:
- 打印script start
- 将setTimeout放入宏任务队列
- 执行async1(),打印async1 start,遇到await,new Promise打印promise1,执行resolve(),将.then放入微任务队列,之后将console.log(‘async1 end’)放入微任务队列,让出执行栈
- new Promise打印promise3,执行resolve(),将.then放入微任务队列
- 打印script end
- 依次执行微任务队列中的任务,打印promise2,async1 end,promise4
- 第一次事件循环结束,执行下一个宏任务,打印setTimeout
因此代码执行结果为
script start
async1 start
promise1
promise3
script end
promise2
async1 end
promise4
setTimeout
练习三(之三)
async function async1() {
console.log('async1 start');
await async2();
//更改如下:
setTimeout(function() {
console.log('setTimeout1')
},0)
}
async function async2() {
//更改如下:
setTimeout(function() {
console.log('setTimeout2')
},0)
}
console.log('script start');
setTimeout(function() {
console.log('setTimeout3');
}, 0)
async1();
new Promise(function(resolve) {
console.log('promise1');
resolve();
}).then(function() {
console.log('promise2');
});
console.log('script end');
看了这么多,相信你已经能独立完成,因此不再赘述执行过程,只贴出结果看看你的想法对吗
注:await后的函数为setTimeout,因此会将其加入宏任务队列中
script start
async1 start
promise1
script end
promise2
setTimeout3
setTimeout2
setTimeout1
练习三(之四)
async function a1 () {
console.log('a1 start')
await a2()
console.log('a1 end')
}
async function a2 () {
console.log('a2')
}console.log('script start')setTimeout(() => {
console.log('setTimeout')
}, 0)Promise.resolve().then(() => {
console.log('promise1')
})a1()let promise2 = new Promise((resolve) => {
resolve('promise2.then')
console.log('promise2')
})promise2.then((res) => {
console.log(res)
Promise.resolve().then(() => {
console.log('promise3')
})
})
console.log('script end')
结果
script start
a1 start
a2
promise2
script end
promise1
a1 end
promise2.then
promise3
setTimeout
推荐阅读
- javascript|promise原理与async 及 await
- React|React UI组件库——如何快速实现antd的按需引入和自定义主题
- React|【React路由】编程式路由导航和withRouter的使用——push / replace
- React|React路由组件传参的三种方式——params、search、state
- React|【React组件】github搜索案例之 父子组件通信 (附源码)
- React|【React】React组件实例的三大属性之state,props,refs(你学废了吗)
- React|【ReactRouter5】路由的模糊匹配,重定向以及嵌套路由
- React|【React】深入理解React组件生命周期----图文详解(含代码)
- echarts|CMS项目数据可视化-echarts的使用