JS是单线程,但是浏览器不是,只是执行JS代码的引擎是个单线程的所以JS的代码没办法开启多个线程。在执行的时候,有且只有一个主线程来处理所有的任务。
浏览器是多线程,异步任务是浏览器开启对应的线程来执行的,最后放入JS引擎中进行执行。
浏览器有定时器线程、事件触发线程、异步http请求线程、GUI线程等。
堆栈(先进后出),队列(先进先出)
执行栈 = 调用栈 = 执行上下文栈
宏任务/微任务
宏任务 Macrotasks
- setTimeout
- setInterval
- setImmediate
- I/O
- UI rendering
微任务 Microtasks
- process.nextTick
- promises
- Object.observe
- MutationObserver
Event Loop
- 整体的代码(main script)块看成宏任务,开始第一次执行宏任务。
- 当遇到异步代码时进入到
event table,并注册其回调函数,接受到响应后将回调函数加入任务队列,此时的同步代码也依次在执行。 - 异步任务分成宏任务和微任务,将分别进入宏任务队列、微任务队列。
- 同步代码执行完后,将执行微任务队列的任务,根据优先级和入队顺序,依次执行,直到微任务队列清空。
- 接着再去宏任务队列拿一个宏任务执行(只拿一个),执行完后。
- 继续看微任务队列是否有任务待执行,清空完所有微任务队列的任务后,再继续执行宏任务。
- 重复(4)(5)就构成了一个循环(event loop),直到所有宏任务队列和微任务队列被清空。
main script的概念,就是一开始执行的代码,被定义为了宏任务,然后根据main script中产生的微任务队列和宏任务队列,先清空微任务的所有队列,再去清空宏任务的队列的一个。
(图片来源于网络)
以下示例代码摘自:传送门
代码一:
setTimeout(()=>{console.log(1)},0)Promise.resolve().then(()=>{console.log(2)})console.log(3)// 打印结果:3 2 1
解析:
- 将定时任务加入宏任务队列
- 将Promise加入微任务队列
- 执行同步代码,打印 3
- 优先执行微任务,打印 2
- 再执行宏任务,打印 1
代码二:
setTimeout(()=>{console.log(1)},0)let a = new Promise((resolve)=>{console.log(2)resolve()}).then(()=>{console.log(3)}).then(()=>{console.log(4)})console.log(5)
解析:
最后打印结果:2, 5, 3, 4, 1
- 将
setTimeout加入宏任务队列。 - 因
Promise的executor是同步的,所以打印 2,执行resolve(),将then加入微任务队列。 - 再打印 5,主代码的同步代码执行完毕。
- 接着优先执行微任务,打印 3,而
Promise的then会始终返回一个resolve,所以这里默认执行resolve(),再将第二个then放到微任务。 - 因为微任务是一次把微任务队列中所有清空,所以继续执行第二个
then的回调,打印 4。 - 此此微任务被清空,执行宏任务的
setTimeout,打印 1。
代码三:
new Promise((resolve,reject)=>{console.log("promise1")resolve()}).then(()=>{console.log("then11")new Promise((resolve,reject)=>{console.log("promise2")resolve()}).then(()=>{console.log("then21")}).then(()=>{console.log("then23")})}).then(()=>{console.log("then12")})
最后打印结果:promise1, then11, promise2, then21, then12, then23
解析:
- 首先执行外层
Promise的executor,输出promise1并将其第一个then加入微任务。 - 执行微任务
then,输出then11,同时执行内层Promise,输出promise2,同时将内层第一个then加入微任务。 - 注意,在此时外层的
Promise的第一个then里的同步代码已执行结束,剩下的是内层最后一个then,它是异步代码,所以此时这里return resolve()(默认的返回),将外层最后一个then加入微任务。 - 此时,微任务里有俩个任务,第一个是内层第一个
then和外层最后一个then。 - 执行第一个微任务,输出
then21,再执行第二个微任务,输出then 12。 - 最后输出
then23 - 在这步最容易产的和问题是
then21、then23、then12谁先执行的问题,搞清楚这里就OK。
代码四:
代码三的变异版本一
new Promise((resolve,reject)=>{console.log("promise1")resolve()}).then(()=>{console.log("then11")return new Promise((resolve,reject)=>{console.log("promise2")resolve()}).then(()=>{console.log("then21")}).then(()=>{console.log("then23")})}).then(()=>{console.log("then12")})
输出:promise1, then11, promise2, then21, then23, then12
- 这里的就是
then12在then23之后了。 - 因为
Promise2进行主动返回了,且返回的是Promise2的最后一个then的结果,就需要将Promise2的所有then执行完后才返回。
代码五:
代码三的变异版本二
new Promise((resolve,reject)=>{ // Promise1console.log("promise1")resolve()}).then(()=>{console.log("then11")new Promise((resolve,reject)=>{ // Promise2console.log("promise2")resolve()}).then(()=>{console.log("then21")}).then(()=>{console.log("then23")})}).then(()=>{console.log("then12")})new Promise((resolve,reject)=>{ // Promise3console.log("promise3")resolve()}).then(()=>{console.log("then31")})
执行结果: promise1, promise3, then11, promise2, then31, then21, then12, then23
这里主要考的是微任务队列的先后问题
执行流程:
- 执行
Promise1和Promise3的executor,输出"promise1","promise3",并将对应then,加入队列- MicroTaskQueue: [
Promise1·then1,Promise3·then1]
- MicroTaskQueue: [
- 执行
Promise1·then1,输出"then11"、"promise2"- MicroTaskQueue: [
Promise3·then1,Promise2·then1,Promise1·then2]
- MicroTaskQueue: [
- 执行
Promise3·then1,输出"then31"- MicroTaskQueue: [
Promise2·then1,Promise1·then2]
- MicroTaskQueue: [
- 执行
Promise2·then1,输出"then21"- MicroTaskQueue: [
Promise1·then2,Promise2·then2]
- MicroTaskQueue: [
- 执行
Promise1·then2,输出"then12"- MicroTaskQueue: [
Promise2·then2]
- MicroTaskQueue: [
- 执行
Promise2·then2,输出"then23"- MicroTaskQueue: []
- 执行结束。
*执行代码六
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
执行流程:
- 主代码执行将
setTimeout放入宏队列。 - 执行
async1(),输出async1 start,遇到await调用async2(),输出async2,会返回一个Promise,在微任务队列加入一个回调,继续执行同步代码。 - 执行
Promise和后面的代码,在微任务队列再放一个Promise回调。 - 执行第一个回调,输入
async1 end,第二个回调promise2
这里的最麻烦的是
async和promise的执行顺序,实际上async和await就是Promise和Generator的语法糖,内部可以按Promise的东西来理解就行。
以上的 async1 函数等同于以下代码:
async function async1(){console.log('async1 start')return new Promise((resolve)=>{console.log('async2')resolve()}).then(()=>{console.log('async1 end')})}
*执行代码七
此代码是代码六的变异版
async function async1() {console.log("async1 start");await async2();console.log("async1 end");}async function async2() {await 'await hello'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");}).then(function () {console.log("promise3");});console.log('script end');
解析:原理基本同 代码六 差不多,唯一就是在 async2() 中加入了 await。async1() 函数等同如下
async function async1() {console.log("async1 start");return new Promise((resolve)=>{return Promise.resolve().then(()=>{console.log( 'async2');resolve()})}).then(()=>{console.log("async1 end");})}
