JS中的事件循环和任务队列

2,288 阅读4分钟

一道题

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

有几个还是写不对。。。

需要注意的几点:

1. Promise优先于setTimeout执行;

2. Promise一旦被定义就会立马执行;

3.  Promise的resolve和reject是异步执行的回调;那么resolve就会被放到任务队列中,在主线程执行完和setTimeout之前执行;

4. await执行完会让出线程,async标记的函数会返回一个Promise对象。

JS的执行


这是一张JS执行机制图

分为:

1. JavaScript Engine,Chrome的引擎是V8;

2. Web APIs:DOM的操作,AJAX,Timeout等实际上调用都是这里提供的;

3. Callback Queue: 回调队列,也就是所有Web APIs里的回调函数,实际上都在这里排队;

4. Event Loop,事件循环,就是所有宏任务和微任务的容器。

5. 微任务会比宏任务执行的更快。

任务队列

JS分同步和异步任务

同步任务都在主线程执行,形成一个执行栈。

对异步的操作,就会有优先级执行顺序,分为宏任务和微任务。

所有同步任务执行完成之后,才会读取任务队列中将可运行的异步任务添加到可执行栈中开始执行。

换个说法:JS执行时一个主线程里会有一个事件循环和事件队列,存放各种要处理的事件信息,通过这个循环不断处理这些事件信息或消息。 


一个Event Loop中可以有一个或多个任务队列Task queue,一个任务队列是多个任务的集合。

宏任务 macro task

(macro)task主要包含:script(整体代码)、setTimeout、setInterval、I/O、UI交互事件、postMessage、MessageChannel、setImmediate(Node.js 环境)

宏任务本质: 参与了事件循环的任务。

微任务: micro task

microtasks: process.nextTick, Promises, MutationObserver

微任务本质:直接在 Javascript 引擎中的执行的,没有参与事件循环的任务。
  1. 是个内存回收的清理任务,使用过 Java 的童鞋应该都很熟悉,只是在 JavaScript 这是V8内部调用的
  2. 就是普通的回调,MutationObserver 也是这一类
  3. Callable
  4. 包括 Fullfiled 和 Rejected 也就是 Promise 的完成和失败
  5. Thenable 对象的处理任务

运行机制


async:

async 是 ES7 才有的与异步操作有关的关键字,和 Promise , Generator 有很大关联。

async function name([param[, param[, ... param]]]) { statements }

参数: name: 函数名; param: 参数 statements: 函数体语句

返回值: 返回一个Promise对象,所以可以用then添加回调函数。

注意: Async函数中可能有await也可以没有。

有await的话: 在Async函数执行的时候,遇到await就暂停,等到触发的异步操作执行完再回复Async的执行并返回解析值。

await 操作符用于等待一个 Promise 对象, 它只能在异步函数 async function 内部使用。


题目的分析:

1. 先出script end 因为上面的async函数还没调用,先不管,而同步的console.log先执行;

2. async1 start 是因为settimeout进入宏任务,而之后调用了async1,async表达式定义的函数也是立即执行 在前面我们说过看到带有async关键字函数,它仅仅是把return值包装成了promise,所以就很普通的打印 console.log( 'async1 start' );

3. async2 , async2是async定义的函数,输出async2并返回promise对象, await后,中断async函数,先执行async外的同步代码, 目前就直接打印 console.log('async2')

4. promise1,Promise构造函数是直接调用的同步代码,所以就打印console.log( 'promise1' )

5. script end,因为上一步先打印了promise1,然后执行到resolve的时候,然后跳出promise继续向下执行,所以就打印console.log( 'script end' )

6. async1 end: 因为await定义的这个promise已经执行完,并且返回结果,所以继续执行async1函数后的任务,就是console.log(‘async1 end’)

7.promise2, 因为前面的new promise放进resolve回调,这个resolve被放到调用栈执行,所以就打印console.log('promise2');

8. settimeout,宏任务最后执行