问题由来
前端JS异步处理,多个异步如何避免“瀑布编程”?如何使用async和await?在延迟处理setTimeout中如何确定执行顺序?
对此引出前端任务执行中任务队列的概念:宏任务、微任务以及主线程。
大佬请移驾
直接上代码:
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');
首先我们需要明白以下几件事情:
JS分为同步任务和异步任务
同步任务都在主线程上执行,形成一个执行栈
主线程之外,事件触发线程管理着一个任务队列,只要异步任务有了运行结果,就在任务队列之中放置一个事件。
一旦执行栈中的所有同步任务执行完毕(此时JS引擎空闲),系统就会读取任务队列,将可运行的异步任务添加到可执行栈中,开始执行。
下面解释一下宏任务和微任务:
宏任务:macrotask(又称之为宏任务),可以理解是每次执行栈执行的代码就是一个宏任务(包括每次从事件队列中获取一个事件回调并放到执行栈中执行)。浏览器为了能够使得 JS 内部 macrotask 与 DOM 任务能够有序的执行,会在一个 macrotask 执行结束后,在下一个 macrotask 执行开始前,对页面进行重新渲染。
macrotask 主要包含:script(整体代码)、setTimeout、setInterval、I/O、UI 交互事件、postMessage、MessageChannel、setImmediate(Node.js 环境)
微任务:microtask(又称为微任务),可以理解是在当前 task 执行结束后立即执行的任务
。也就是说,在当前 task 任务后,下一个 task 之前,在渲染之前。所以它的响应速度相比setTimeout(setTimeout是task)会更快,因为无需等渲染。也就是说,在某一个 macrotask 执行完后,就会将在它执行期间产生的所有 microtask 都执行完毕(在渲染前)。microtask 主要包含:Promise.then、MutaionObserver、process.nextTick(Node.js 环境)
请给出以上代码执行结果:
说实话 之前遇到这种问题直接就是懵逼的,只知道async异步操作,setTimeout延迟执行,但是延迟0秒我都没有遇到和试过。
上结果:
如果明白此结果的大佬可以互相的讨论讨论,我说的只是我自己的理解,下面根据我自己的理解来给出执行流程。
- 都知道的JS执行顺序,主线程从上往下依次执行首先遇到打印
console.log('script start')
,方法未调用不执行这个不用多说哈,继续往下执行; - 然后遇到
setTimeout
,由于它是个宏任务,则先进入宏任务队列中,继续执行; - 调用方法
async1
,由于方法立即执行则执行console.log('async1 start')
,然后方法内继续执行await async2();
因为遇到await,await会让下面的内容进入微任务中,则将console.log('async1 end')
放入微任务队列中,而await还是会立即执行,则执行方法async2()
,方法中立即调用console.log('async2');
;则当前线程继续往下执行; - 遇到
new Promise
对象,对象内容立即执行console.log('promise1');
,而对象.then是一个微任务,则将内容console.log('promise2');
放入微任务中,线程继续往下; - 最后遇到
console.log('script end');
,则继续打印。当前线程执行结束。 - 当前线程结束打印结果为:
script start;async1 start;async2;promise1;script end
这是线程执行结束的结果,由于微任务和宏任务中还没有执行结束,所以继续执行微任务中的任务;继续依次打印微任务中的数据:async1 end;promise2;
当微任务执行结束后继续执行宏任务:setTimeout
- 最后的结果:script start;async1 start;async2;promise1;script end;async1 end;promise2;setTimeout
下面图片解释执行过程
其中数字代表线程执行遇到的顺序,主线程第一次执行结束后,再次执行微任务队列中的任务,最后执行的是宏任务队列中的任务。
下面再通过一个例子去更深次理解宏任务和微任务。
PS:
async function async1() {
console.log('async1 start');
await async2();
console.log('async1 end');
}
async function 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');
以上例子在基础上做了小小的改动,直接上图片解释结果:
所以他的结果是:
对不起 ~ 我只是一个菜鸟,如果有解释不清或者有问题的请使劲喷我!