JavaScript运行在一个单线程上

  1. 所有同步任务都在主线程执行,形成一个执行栈
  2. 主线程之外,还存在任务队列(task queue)。只要异步任务有了运行结果,就会在任务队列之中放置一个事件。
  3. 一旦执行栈中的所有同步任务执行完毕,系统就会读取任务队列,其中对应的异步任务结束等待状态,进入执行栈开始执行
  4. 主线程不断重复上面的第三步。

这个过程被称为事件循环(event-loop)

JavaScript中有两种异步任务

宏任务浏览器NodeJs
I/O
setTimeout
setInterval
setImmediate×
requestAnimationFrame×
微任务浏览器NodeJs
process.nextTick×
MutationObeserver×
Promise

清空宏任务微任务的事件正是事件循环(event-loop)
在执行完执行栈内的任务后,会立刻先处理所有微任务队列中的事件, 然后再去宏任务队列中取出一个事件。同一次事件循环中, 微任务永远在宏任务之前执行。
微任务队列清空完后,选择任务队列中的下一个宏任务,宏任务执行完后检查微任务队列

Event-Loop

一个例子

/* 1*/    const next = msg => {
/* 2*/        return new Promise(resolve => {
/* 3*/            console.log(msg);
/* 4*/            resolve(msg + ' resolve');
/* 5*/        }).then(value => {
/* 6*/            console.log(value);
/* 7*/        })
/* 8*/    }
/* 9*/    const one = async next => {
/* 10*/        console.log('>> one');
/* 11*/        await next('do one');
/* 12*/        console.log('<< one');
/* 13*/    }
/* 14*/    const two = next => {
/* 15*/        console.log('>> two');
/* 16*/        next('do two').then(() => {
/* 17*/            console.log('<< two');
/* 18*/        })
/* 19*/    }
/* 20*/    one(next);
/* 21*/    two(next);

结果如下:

    // >> one
    // do one
    // >> two
    // do two
    // do one resolve
    // do two resolve
    // << two
    // << one

让我们捋一下代码执行过程

先是全局执行环境中

第20行,进入了one函数

跳转至第10行

第10行,打印>> one

第11行,进入next函数

跳转至第3行

第3行,打印do one

第4行,添加微任务,此时微任务队列简略概括为['do one resolve' ]

弹出next函数、弹出one函数

跳转至第21行

第21行,进入了two函数

跳转至第15行

第15行,打印>> two

第16行,进入next函数

跳转至第3行

第3行,打印do two

第4行,添加微任务,此时微任务队列简略概括为["do one resolve", "do two resolve" ]

弹出next函数

跳转至第16行

第16行,添加微任务,此时微任务队列简略概括为["do one resolve", "do two resolve", "<< two" ]

执行栈为空,此时开始清空微任务队列

分别打印:

do one resolve

do two resolve

<< two

此时await返回的Promise对象状态为Resolved,

跳转至11行,

第12行,打印<< one

参考资料:微任务、宏任务与Event-Loop