总结:事件循环,宏任务,微任务

140 阅读5分钟

一、宏任务、微任务

参考文章:什么是宏任务、微任务?宏任务、微任务有哪些?又是怎么执行的?-CSDN博客

a.二者对比

b.事件循环

1>宏任务中有微任务

  1. 宏任务(例如:setTimeout、I/O 操作、UI 渲染等)会首先执行。
  2. 在执行宏任务的过程中,如果有微任务被创建(例如在宏任务中执行 Promise.resolve()queueMicrotask()),这些微任务会被添加到 微任务队列
  3. 在当前的宏任务执行完之后,所有的微任务会在下一个宏任务执行之前依次执行,直到微任务队列为空。
  4. 然后,再开始执行下一个宏任务。
    console.log("Start");
​
    setTimeout(() => {
        console.log("Macro task 1");
​
        // 在宏任务中创建微任务
        Promise.resolve().then(() => {
            console.log("Microtask inside Macro task 1");
        });
​
        console.log("End of Macro task 1");
    }, 0);
​
    setTimeout(() => {
        console.log("Macro task 2");
    }, 0);
​
    console.log("End");
​
输出结果:---->Start / End /  Macro task 1 / End of Macro task 1 / Microtask inside Macro task 1 / Macro task 2

2>宏任务中有宏任务

    console.log("Start");
​
    setTimeout(() => {
        console.log("Macro task 1");
​
        Promise.resolve().then(() => {
            console.log("Microtask inside Macro task 1");
​
            // 创建一个宏任务
            setTimeout(() => {
                console.log("Macro task 1.1");
            }, 0);
​
            // 创建另一个微任务
            Promise.resolve().then(() => {
                console.log("Microtask 1.1");
            });
​
            // 再次创建宏任务
            setTimeout(() => {
                console.log("Macro task 1.2");
            }, 0);
        });
​
        console.log("End of Macro task 1");
    }, 0);
​
    setTimeout(() => {
        console.log("Macro task 2");
​
        // 在宏任务 2 中创建微任务
        Promise.resolve().then(() => {
            console.log("Microtask inside Macro task 2");
​
            // 嵌套一个宏任务
            setTimeout(() => {
                console.log("Macro task 2.1");
            }, 0);
        });
​
        console.log("End of Macro task 2");
    }, 0);
​
    setTimeout(() => {
        console.log("Macro task 3");
    }, 0);
​
    console.log("End");

3>混合示例

    console.log("Start");
​
    setTimeout(() => {
        console.log("Macro task 1");
​
        // 在宏任务 1 中使用 process.nextTick
        process.nextTick(() => {
            console.log("process.nextTick inside Macro task 1");
        });
​
    }, 0);
​
    setTimeout(() => {
        console.log("Macro task 2");
​
        // 在宏任务 2 中创建微任务
        Promise.resolve().then(() => {
            console.log("Microtask inside Macro task 2");
​
            // 在微任务中创建新的宏任务
            setTimeout(() => {
                console.log("Macro task 2.1");
            }, 0);
        });
​
    }, 0);
​
    // 在外部创建微任务
    Promise.resolve().then(() => {
        console.log("Microtask 1");
​
        // 在微任务 1 中创建新的微任务
        Promise.resolve().then(() => {
            console.log("Microtask 1.1");
​
            // 在微任务 1.1 中创建宏任务
            setTimeout(() => {
                console.log("Macro task 3");
            }, 0);
        });
​
        // 使用 async/await 创建微任务
        (async () => {
            console.log("Async/Await in Microtask 1");
            await Promise.resolve();
            console.log("After await in Microtask 1");
        })();
    });
​
    // 使用 async/await 创建宏任务
    //异步立即执行函数
    (async () => {
        console.log("Async/Await in Macro task");
        await Promise.resolve();
        console.log("After await in Macro task");
    })();
​
    console.log("End");
​

4>总结

  1. 执行同步任务(立即执行函数等),然后遇到宏任务添加到宏任务队列,遇到微任务添加到微任务队列。

  2. 在执行宏任务之前,微任务队列会被清空。这意味着每次执行宏任务时,都会先执行完所有的微任务。

  3. 执行微任务队列时,如果遇到宏任务,会将其添加到宏任务队列,遇到微任务则会继续添加到微任务队列。这个过程会一直持续,直到微任务队列为空。

  4. 执行宏任务队列:

    执行宏任务队列中的任务。每次从宏任务队列中取出一个任务执行。

    执行过程中如果遇到 宏任务,则继续将其加入宏任务队列;遇到 微任务,则将其加入微任务队列中。

    每次执行完一个宏任务后,会去执行微任务队列中的所有任务。然后继续执行下一个宏任务,重复3,直到宏任务队列为空。

c.接下来我们随便玩几个栗子叭

./one

    console.log('1');
​
    setTimeout(function () {
        console.log('2');
       
        new Promise(function (resolve) {
            console.log('4');
            resolve();
        }).then(function () {
            console.log('5')
        })
    })
    new Promise(function (resolve) {
        console.log('7');
        resolve();
    }).then(function () {
        console.log('8')
    })
​
    setTimeout(function () {
        console.log('9');
      
        new Promise(function (resolve) {
            console.log('11');
            resolve();
        }).then(function () {
            console.log('12')
        })
    })
​
// 结果:1 7 8 2 4 5 9 11 12

./two

setTimeout(()=>{
  new Promise(resolve =>{
    resolve();
  }).then(()=>{
    console.log('test');
  });
​
  console.log(4);
});
​
new Promise(resolve => {
  resolve();
  console.log(1)
}).then( () => {
  console.log(3);
  Promise.resolve().then(() => {
    console.log('before timeout');
  }).then(() => {
    Promise.resolve().then(() => {
      console.log('also before timeout')
    })
  })
})
console.log(2);
​

./three

接下来我们对同一个代码片段进行改动,来判断输出结果,(我将对主要修改地方进行说明)

async function fn1() {
    console.log(1)
    let res = await fn2()
    console.log(res)
    console.log(2)
}
async function fn2() {
​
//同步任务
    return new Promise(() => { console.log("fn2") })
​
}
fn1()
console.log(3)
// 1 fn2 3 //此时不打印2,是因为fn1 返回了一个永远没有解决的promise!!!!!!!!!!,console.log(2)执行任务被阻塞//哇塞是promise我们有救了

我们进行改动

async function fn1() {
    console.log(1)
    let res = await fn2()
    console.log(res)
    console.log(2)
}
async function fn2() {
    return Promise.resolve("fn2")
}
fn1()
console.log(3)
//1 3 fn2 2 

再来改动

    async function fn1() {
        console.log(1)
        let res = await fn2()
        console.log(res)
        console.log(2)
    }
    async function fn2() {
​
        //同步任务
        setTimeout(() => {
            console.log("fn2")
        })
​
    }
    fn1()
    console.log(3)
// 1 3  2 fn2 
大灰狼请注意---->asyncPromise 之间的关系
  • async 函数会自动将函数的返回值包装成 Promise。无论你在 async 函数中是直接返回一个值,还是返回一个 Promiseasync 函数都会将其包装成 Promise,并且它会在执行完成后根据返回值的类型,决定是否将这个 Promise 的状态设置为已解决(fulfilled)或已拒绝(rejected)。
    • 如果 async 函数内有 returnreturn 的值会被自动封装在一个已解决(fulfilled)的 Promise 中。
    • 如果 async 函数内发生异常,则会返回一个已拒绝(rejected)的 Promise,异常作为拒绝的原因。

./four

    function block(delay) {
        const start = Date.now()
        while (Date.now() - start < delay) { window.timestamp = Date.now() }
    }
​
    function a() {
        console.log(1)
        block(1000)//同步阻塞
        setTimeout(() => console.log(2), 0)
        setTimeout(() => console.log(3), 1)
    }
​
    function b() {
        console.log(4)
    }
    console.log(5)
    setTimeout(a(), 0)
    setTimeout(b(), 500)
// 5 1 4 2 3

我们对其中一部分代码进行修改

    function block(delay) {
        const start = Date.now()
        while (Date.now() - start < delay) { window.timestamp = Date.now() }
    }
​
    function a() {
        console.log(1)
        block(1000)//同步阻塞
        setTimeout(() => console.log(2), 0)
        setTimeout(() => console.log(3), 1)
    }
​
    function b() {
        console.log(4)
    }
    console.log(5)
    setTimeout(a, 0)!!!!!!!注意,这里进行了修改
    setTimeout(b, 500)
// 5 1 2 4 3//多个宏任务(如 setTimeout)的执行顺序与它们的第二个参数(即延迟时间)是相关的,但是它并不是唯一的决定因素

最后一个,太棒了,马上就到终点了!撒花!!!

async function async1() {
    console.log('async1 start')
    await async2()
    console.log('async1 end' )
    setTimeout(() => {
        console.log('timer1')
    }, 0)
}
​
async function async2() {
    setTimeout(() => {
        console.log('timer2')
    }, 0)
    console.log('async2')
}
​
async1()
setTimeout(() => {
    console.log('timer3')
}, 0)
​
console.log('start')