0%

js的执行顺序事件循环

在牛客上看的面试题,引发的……

async function async1() {
    console.log('async1 start');
    await async2();
    console.log('async1 end');
}

async function async2() {
    console.log('async2 start');
    return new Promise((resolve, reject) => {
        resolve();
        console.log('async2 promise');
    })
}

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')

根据这道题整理了以下知识点

js执行机制

JavaScript是一门单线程语言,是按照语句的顺序自上而下进行执行的。将任务可以分为两类

  • 同步任务:需要执行的任务在主线程上排队,直接执行。
  • 异步任务:没有立即执行的任务但是需要被执行的任务,放在任务队列里面

JavaScript事件循环

  • 所有同步任务在主线程上执行,形成执行栈
  • 主线程之外还有一个任务队列(task queue),负责执行异步任务,任务队列分为宏任务(macro-task)和微任务(micro-task)
  • 一旦执行栈中同步任务执行完毕,就会开始执行任务队列

宏任务和微任务

macro-task(宏任务):

  • 包括整体代码script
  • setTimeout
  • setInterval
  • setImmediate (Node独有)
  • requestAnimationFrame (浏览器独有)
  • I/O
  • UI rendering (浏览器独有)

micro-task(微任务):

  • Promise
  • process.nextTick(Node独有)
  • Object.observe
  • MutationObserver

不同的任务会进入不到不同的event queue

而任务队列的执行如上图所示,当同步任务执行完,开始执行任务队列

  • 是否有可执行的微任务
    • 如果有,执行所有的微任务
    • 如果没有,开始执行新的宏任务
  • 当宏任务执行完,再回到 ‘是否有可执行的微任务’

所以任务队列会开优先执行微任务,只有当所有的微任务执行完毕才会执行一条宏任务

代码演示:

setTimeout(function() {
    console.log('setTimeout');
})

new Promise(function(resolve) {
    console.log('promise');
    resolve(true)
}).then(function() {
    console.log('then');
})

console.log('console');

// promise
// console
// then
// setTimeout

Promise的resolve和reject

promise中的代码是同步的,异步体现在resolve和reject

而promise中的resolve会放到任务队列的微任务中

注意,调用resolvereject并不会终结 Promise 的参数函数的执行。

代码演示:

new Promise((resolve, reject) => {
  resolve(1);
  console.log(2);
}).then(r => {
  console.log(r);
});
// 2
// 1

上面代码中,调用resolve(1)以后,后面的console.log(2)还是会执行,并且会首先打印出来。这是因为立即 resolved 的 Promise 是在本轮事件循环的末尾执行,总是晚于本轮循环的同步任务。

async await

async函数中的代码,在出现await之前也是同步执行的。

async await本身是promise + generator的语法糖,而await 后面必须跟Promise对象

所以await后面的代码是放到微任务中的,这个时候就会跳出async函数,继续执行后面的代码。

面试题中的

async function async1() {
    console.log('async1 start');
    await async2();
    console.log('async1 end');
}

相当于

async function async1() {
    console.log('async1 start')
    // Promise.resolve(async2()).then(() => {
    //    console.log('async1 end')
    //})
    new Promise((reslove) => {
        reslove(async2())
    }).then(() => {
        console.log('async1 end')
    })
}

回到面试题

async function async1() {
    console.log('async1 start');
    await async2();
    console.log('async1 end');
}

async function async2() {
    console.log('async2 start');
    return new Promise((resolve, reject) => {
        resolve();
        console.log('async2 promise');
    })
}

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')

1.开始执行同步语句:

  • step1

    console.log('script start');

    执行栈:[console.log(‘script start’)]

    宏任务: []

    微任务: []

    打印结果:
    script start
    
  • step2

    setTimeout(function() {
        console.log('setTimeout');
    }, 0);

    执行栈:[]

    宏任务: [setTimeout]

    微任务: []

  • step3

    async1();

    开始执行async1()

    await async2()可以看作是一个new Promise()对象

执行栈:[console.log(‘async1 start’)]

宏任务: [setTimeout]

微任务: [Promise1.resolve(async2()), Promise1.resolve(async2()).then(()=>console.log(‘async1 end’) )]

  打印结果:
  script start
  async1 start
  • step4

    执行async2()

执行栈:[console.log(‘async2 start’), console.log(‘async2 promise’)]

宏任务: [setTimeout]

微任务: [Promise.resolve(), Promise.resolve().then(() => console.log(‘async1 end’) )]

  打印结果:
  script start
  async1 start
  async2 start
  async2 promise
  • step5

    顺序执行new Promise()

    执行栈:[console.log(‘promise1’),console.log(‘script end’)]

    宏任务: [setTimeout]

    微任务:[Promise.resolve(), Promise.resolve().then(() => console.log(‘async1 end’) ), Promise.reslove(‘promise2’) , Promise.reslove(‘promise3’)]

    打印结果:
    script start
    async1 start
    async2 start
    async2 promise
    promise1
    script end
  • setp6

    再开始执行宏任务前,看微任务队列是否为空。开始执行微任务。

    Promise.resolve(),Promise.reslove(‘promise2’) , Promise.reslove(‘promise3’)是同级的。

    打印结果:
    script start
    async1 start
    async2 start
    async2 promise
    promise1
    script end
    promise2
    promise3

    由于微任务队列还不为空,继续执行微任务Promise.resolve().then(() => console.log(‘async1 end’)

    打印结果:
    script start
    async1 start
    async2 start
    async2 promise
    promise1
    script end
    promise2
    promise3
    async1 end
  • step7

    微任务队列为空,开始执行宏任务

    打印结果:
    script start
    async1 start
    async2 start
    async2 promise
    promise1
    script end
    promise2
    promise3
    async1 end
    setTimeout

总结

执行任何一个宏任务之前都会查看微任务队列,只有微任务队列为空才会执行宏任务。

async await 本身是promise +generator的语法糖,可以转化为new Promise进行分析。

参考:

js执行机制(promise,setTimeout执行顺序)

阮一峰的ES6入门