为什么要引入同步与异步的概念?
首先JS是一门单线程的语言?这也就意味着js在同一时间只能执行一个任务。若同时存在多个任务,这些任务需要排队执行。但是在js中存在着一些耗时的代码,我们不可能等着这些耗时的代码执行完成以后再执行之后的代码。所以就有了同步和异步的概念。
在说同步任务与异步任务之前,我们首先要明确一下几个概念:
主线程:执行任务的区域。也被称为执行栈。
任务队列:主线程外开辟的一个存放异步任务的地方。分为宏任务队列和微任务队列。
同步任务:在主线程执行的任务,这些任务会按顺序执行,只有在上一个任务结束后,才会执行下一个任务。无需耗时的操作称为同步任务。
异步任务:存放在任务队列中,只有当主线程中的所有同步任务执行完成,才会读取任务队列中的异步任务执行。 大多是需要耗时的操作称为异步任务。
宏任务:一般包括:script(整个js脚本),setTimeout,setIntereval,dom事件的回调函数,ajax。
微任务:一般包括:紧跟在async await之后执行任务,promise的then,catch回调函数,nextTick函数
// 这里我们来看一个例子,会输出0-9,求输出顺序
async function async1() {
console.log('0');
await async2();
console.log('1');
}
async function async2() {
console.log('2');
}
console.log('3');
setTimeout(function (){
console.log('4');
new Promise(function(resolve) {
console.log('5');
resolve();
}).then(function() {
console.log('6');
});
},0);
async1();
new Promise(function(resolve) {
console.log('7');
resolve();
}).then(function() {
setTimeout(function() {
console.log('8');
}, 0);
});
console.log('9');
解决此题,我们首先要了解同步任务与异步任务,宏任务与微任务的事件循环机制。
1.先执行主线程中的同步任务,并将宏任务和微任务划分到各自队列中。
2.当主线程中的同步任务执行完成后,立即依次执行微任务队列中所有微任务。
3.然后读取宏任务队列中最靠前的宏任务进入执行栈中。
4.执行宏任务的过程中,遇到了微任务,会将其依次加入微任务队列。
5.当宏任务执行完后,再次读取微任务队列里的任务,直至队空。然后再次读取宏任务队列。如此循环往复,直至宏任务全部执行完毕。
题目解析:(应为书写空间有限,重复代码不全写)
1.在上面的例题中,我们应该可以看出首先执行了同步任务:console.log('3');,所以我们现在输出的值为“3”。
2.接下来应该执行setTimeout,但是它是宏任务所以要放到宏任务队列中。宏任务队列:setTimeout(function (){ console.log('4'); new Promise...},0); 微任务队列:空
3.接下来执行async1函数,输出的值为“3,0”。执行函数内await async2(),输出的值为“3,0,2”。该任务以后的任务:console.log('1');为微任务,所以放到微任务队列中。所以现在宏任务队列:setTimeout(function (){ console.log('4'); new Promise...},0); 微任务队列:console.log('1');
4.执行new Promise函数,输出的值为“3,0,2,7”。遇到了回调函数then,将其放入微任务队列中。所以现在宏任务队列:setTimeout(function (){ console.log('4'); new Promise...},0); 微任务队列:console.log('1'); then(function() { setTimeout(function() {console.log('8'); }, 0); })。
5.执行同步任务:console.log('9');,输出的值为:“3,0,2,7,9”。到此为值整个脚本(算作一个宏任务)的同步任务执行完成。开始执行微任务。
6.先执行微任务队列中的console.log('1');,输出的值为:“3,0,2,7,9,1”。再执行微任务队列then函数中的setTimeout,由于它是宏任务,所以放到宏任务队列中。所以现在宏任务队列:setTimeout(function (){ console.log('4'); new Promise...},0); setTimeout(function() {console.log('8'); }, 0);微任务队列:空。
7.由于微任务队列为空,开始取宏任务队列中的第一个任务:setTimeout(function (){ console.log('4'); new Promise...},0);,输出值为“3,0,2,7,9,1,4”。遇到了new Promise,执行任务,输出值为:“3,0,2,7,9,1,4,5”。该任务有回调函数then,放到微任务队列中。所以现在宏任务队列: setTimeout(function() {console.log('8'); }, 0);微任务队列:.then(function() { console.log('6'); });。
8.微任务队列中有了新的任务,所以要执行微任务队列中的任务,直至清空,才可以执行宏任务队列。所以输出的值为:“3,0,2,7,9,1,4,5,6”。所以现在宏任务队列: setTimeout(function() {console.log('8'); }, 0);微任务队列:空。
9.由于微任务队列为空,执行宏任务队中第一个任务。输出的值为:“3,0,2,7,9,1,4,5,6,8”。由于宏任务和微任务队列的值都为空,所以执行结束。最终输出结果为:“3,0,2,7,9,1,4,5,6,8”。
代码执行过程中的两个坑:
1. 宏任务的优先级高于微任务,而整个脚本文件算作一个宏任务,所以在同步任务执行结束后,会执行微任务队列中的任务。
2.async await 右边的任务会立马执行,紧跟在await后面的任务,会放在微任务队列中。
总结:综上所述同步任务与异步任务简单来说就是任务排列顺序与任务执行顺序不同。其本质就是在主线程上任务的执行顺序。
视频讲解 https://www.bilibili.com/video/BV1Vs4y1z7AS/?vd_source=be4efb0f2cfe5e83a1774731474196fd