目录
js是单线程语言
同步任务 (synchronous)
异步任务(asynchronous)
事件循环(Event Loop)
宏任务(macrotask)
微任务(microtask)
promise
Async/await
题一
题二
题三
题四
结尾
参考文献:
进程,即资源分配的最小单位,拥有独立的堆栈空间和数据存储空间
线程,即程序执行的最小单位,一个进程可以包括多个线程
如果多进程,同时操作DOM,那么后果就不可控了
例如:对于同一个按钮,不同的进程赋予了不同的颜色,到底该怎么展示
单线程就意味着,所有任务需要排队,前一个任务结束,才会执行后一个任务。如果前一个任务耗时很长,后一个任务就不得不一直等着
JavaScript语言的设计者意识到,这时主线程完全可以不管IO设备,挂起处于等待中的任务,先运行排在后面的任务。等到IO设备返回了结果,再回过头,把挂起的任务继续执行下去。
于是,所有任务可以分成两种,一种是同步任务(synchronous),另一种是异步任务(asynchronous)。
同步任务指的是,在主线程上排队执行的任务,只有前一个任务执行完毕,才能执行后一个任务
异步任务指的是,不进入主线程,而进入”任务队列”(task queue)的任务,只有”任务队列”通知主线程,某个异步任务可以执行了,该任务才会进入主线程执行。
- 同步和异步任务分别进入不同的执行"场所",同步的进入主线程,异步的进入Event Table并注册函数。
- 当指定的事情完成时,Event Table会将这个函数移入Event Queue。
- 主线程内的任务执行完毕为空,会去Event Queue读取对应的函数,进入主线程执行。
- 上述过程会不断重复,也就是常说的Event Loop(事件循环)。
当前调用栈中执行的代码成为宏任务。 包括:script 中代码、setTimeout、setInterval、I/O、UI render
当前(此次事件循环中)宏任务执行完,在下一个宏任务开始之前需要执行的任务,可以理解为回调事件
包括:process.nextTick、MutationObserver、Promise.then
Promise中的异步体现在then和catch中,所以写在Promise中的代码是被当做同步任务立即执行的
new Promise(function(resolve) {
console.log('promise'); // 此处立即执行
resolve();
}).then(res=>{
console.log('rosolve') // 加入到微任务(microtask中)中
})
而在async/await中,在await出现之前,其中的代码也是立即执行的。 很多人以为await会一直等待之后的表达式执行完之后才会继续执行后面的代码,实际上await是一个让出线程的标志。 await后面的表达式会先执行一遍,将await后面的代码加入到微任务microtask中,然后就会跳出整个async函数来执行后面的代码。 因为async await 本身就是promise+generator的语法糖。所以await后面的代码是microtask
async function async1() {
console.log('async1 start'); //立即执行
await async2(); //立即执行await后面的表达式
console.log('async1 end'); //将await后面的代码加入到微任务(microtask中)中
}
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');
/*
script start
async1 start
async2
promise1
script end
async1 end
promise2
setTimeout
*/
解析:
1.事件循环从宏任务(macrotask)队列开始,宏任务队列中,只有一个script(整体代码)任务,执行整个代码块
2.遇到2个定义的async函数,继续往下走,遇到console语句,直接输出 'script start'
3.script任务继续向下执行,遇到async1()函数,async函数中在await之前的代码是立即执行的,所以输出 'async1 start',然后执行async2()函数,输出 'async2',将 ‘console.log('async1 end')’分配到microtask队列中
4.script任务继续往下执行,遇到Promise,Promise中的代码是被当做同步任务立即执行的,所以输出 'promise1',然后执行 resolve,将 'promise2' 分配到microtask队列中
5.script任务继续往下执行,最后只有一句输出了'script end',至此,全局任务就执行完毕了
6.每次执行完一个宏任务之后,会去检查是否存在 microtask;如果有,则执行 microtask 直至清空 microtask Queue。因此在script任务执行完毕之后,开始查找清空微任务队列
7.microtask队列中,Promise 队列中有两个任务async1 end和promise2,因此依次输出async1 end 、promise2,所有的 microtask 执行完毕之后,表示第一轮的循环就结束了
8.第二轮循环依旧从宏任务队列开始。此时宏任务中只有一个 setTimeout,取出直接输出即可,至此整个流程结束
和题一不同的是async2()函数内部也写成了Promise,Promise中的代码是被当做同步任务立即执行的,所有会输出 'promise1','promise2'则被分配到microtask队列中,因此输出完 'script end'后,会相继输出 promise2, async1 end ,promise4,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
async1 start
promise1
promise3
script end
promise2
async1 end
promise4
setTimeout
*/
解析:
在输出为promise2之后,接下来会按照加入setTimeout队列的顺序来依次输出,通过代码我们可以看到加入顺序为3 2 1,所以会按3,2,1的顺序来输出。
sync 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');
/*
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
*/
解析:
1. Promise.resolve().then(() => {
console.log("promise1");
});
a1();
'promise1'也是被分配到microtask队列中的,而且是在a1()函数执行前,先分配的,所以在 'script end'输出后,会先输出 'promise1' 然后在依次输出
2. let promise2 = new Promise((resolve) => {
resolve("promise2.then");
console.log("promise2");
});
这里会直接输出"promise2", 'resolve'不同于'await',不会阻止后面的执行
如果此篇文章对你小伙伴所帮助的话,小伙伴们手动点赞啊
后续会继续更新前端相关文章,感谢小伙伴支持
axios二次封装详解https://blog.csdn.net/qq_56989560/article/details/125032315?spm=1001.2014.3001.5501
https://juejin.cn/post/6921606187116920845#heading-13https://juejin.cn/post/6921606187116920845#heading-13