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 对象状态为 Re­solved,

跳转至 11 行,

第 12 行,打印 << one

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