Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

js event loop 事件循环 #24

Open
kangkai124 opened this issue May 30, 2019 · 5 comments
Open

js event loop 事件循环 #24

kangkai124 opened this issue May 30, 2019 · 5 comments
Labels

Comments

@kangkai124
Copy link
Owner

kangkai124 commented May 30, 2019

image

参考:

  1. 第 10 题:常见异步笔试题
  2. 这一次,彻底弄懂 JavaScript 执行机制
@kangkai124
Copy link
Owner Author

首先需要明白,JavaScript 是一门单线程语言,分同步任务和异步任务。

image

  • 同步和异步任务分别进入不同的执行"场所",同步的进入主线程,异步的进入Event Table并注册函数。

  • 当指定的事情完成时,Event Table会将这个函数移入Event Queue。

  • 主线程内的任务执行完毕为空,会去Event Queue读取对应的函数,进入主线程执行。

  • 上述过程会不断重复,也就是常说的Event Loop(事件循环)。

根据规范,事件循环是通过任务队列的机制来进行协调的。

一个 Event Loop 中,可以有一个或者多个任务队列(task queue),一个任务队列便是一系列有序任务(task)的集合;每个任务都有一个任务源(task source),源自同一个任务源的 task 必须放到同一个任务队列,从不同源来的则被添加到不同队列。

setTimeout/Promise 等API便是任务源,而进入任务队列的是他们指定的具体执行任务。

宏任务

(macro)task(又称之为宏任务),可以理解是每次执行栈执行的代码就是一个宏任务(包括每次从事件队列中获取一个事件回调并放到执行栈中执行)。

浏览器为了能够使得 JS 内部 (macro)task 与 DOM 任务能够有序的执行,会在一个 (macro)task 执行结束后,在下一个 (macro)task 执行开始前,对页面进行重新渲染,流程如下:

(macro)task->渲染->(macro)task->...

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

微任务

microtask(又称为微任务),可以理解是在当前 task 执行结束后立即执行的任务。也就是说,在当前 task 任务后,下一个 task 之前,在渲染之前。

所以它的响应速度相比setTimeout(setTimeout是task)会更快,因为无需等渲染。也就是说,在某一个macrotask执行完后,就会将在它执行期间产生的所有 microtask 都执行完毕(在渲染前)。

microtask主要包含:Promise.then、MutaionObserver、process.nextTick(Node.js 环境)

事件循环,宏任务,微任务的关系如图所示:

image

Promise和async中的立即执行

Promise 中的异步体现在thencatch中,所以写在Promise中的代码是被当做同步任务立即执行的。

await 是一个让出线程的标志。await 后面的表达式会先执行一遍,将await后面的代码加入到microtask中,然后就会跳出整个async函数来执行后面的代码。

由于因为async await 本身就是 promise+generator 的语法糖。所以 await 后面的代码是 microtask。所以

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

@kangkai124
Copy link
Owner Author

举个栗子:

console.log('1');

setTimeout(function() {
    console.log('2');
    process.nextTick(function() {
        console.log('3');
    })
    new Promise(function(resolve) {
        console.log('4');
        resolve();
    }).then(function() {
        console.log('5')
    })
})
process.nextTick(function() {
    console.log('6');
})
new Promise(function(resolve) {
    console.log('7');
    resolve();
}).then(function() {
    console.log('8')
})

setTimeout(function() {
    console.log('9');
    process.nextTick(function() {
        console.log('10');
    })
    new Promise(function(resolve) {
        console.log('11');
        resolve();
    }).then(function() {
        console.log('12')
    })
})

@kangkai124
Copy link
Owner Author

首先,事件循环从宏任务(macrotask)队列开始,这个时候,宏任务队列中,只有一个script(整体代码)任务;当遇到任务源(task source)时,则会先分发任务到对应的任务队列中去。所以,上面例子的第一步执行如下图所示:

image

然后进入第一轮时间循环:

  • 遇到 console.log(1),输出 1
  • 接着遇到 setTimeout,将其任务放在 macrotask 队列中,记为 setTimeout1
  • 遇到 process.nextTick() ,放在 microtask 中,记为process1
  • 遇到 Promise,Promise 中的函数是立即执行的,所以输出 7,then 放在 microtask 中,记为 then1
  • 然后又遇到了 setTimeout,放在 macrotask 中,记为 setTimeout2

image

上图是第一轮事件循环宏任务结束时各Event Queue的情况,此时已经输出了1和7。我们发现了process1then1两个微任务。

  • 执行process1,输出6
  • 执行then1,输出8

第一轮事件循环正式结束,这一轮的结果是输出1,7,6,8。

那么第二轮时间循环从setTimeout1宏任务开始:

  • 首先输出2
  • 遇到了process.nextTick(),将其放在 microtask 中,记为process2
  • new Promise立即执行输出 4,then也放在 microtask 中,记为then2

image

第二轮事件循环宏任务结束,我们发现有process2then2两个微任务可以执行。

  • 输出3
  • 输出5

第二轮事件循环结束,第二轮输出2,4,3,5。

第三轮事件循环开始,此时只剩 setTimeout2 了,执行。

  • 直接输出9
  • process.nextTick()放在 microtask 中,记为process3
  • 直接执行new Promise,输出 11,将then 放在microtask 中,记为then3

image

第三轮事件循环宏任务执行结束,执行两个微任务process3then3

  • 输出10
  • 输出12

第三轮事件循环结束,第三轮输出9,11,10,12。

整段代码,共进行了三次事件循环,完整的输出为1,7,6,8,2,4,3,5,9,11,10,12。

@kangkai124
Copy link
Owner Author

再举几个栗子吧

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');
async function async1() {
    console.log('async1 start');
    await async2();
    console.log('async1 end');
}
async function async2() {
    //async2做出如下更改:
    new Promise(function(resolve) {
    console.log('promise1');
    resolve();
}).then(function() {
    console.log('promise2');
    });
}
console.log('script start');

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

new Promise(function(resolve) {
    console.log('promise3');
    resolve();
}).then(function() {
    console.log('promise4');
});

console.log('script end');
async function async1() {
    console.log('async1 start');
    await async2();
    //更改如下:
    setTimeout(function() {
        console.log('setTimeout1')
    },0)
}
async function async2() {
    //更改如下:
	setTimeout(function() {
		console.log('setTimeout2')
	},0)
}
console.log('script start');

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

new Promise(function(resolve) {
    console.log('promise1');
    resolve();
}).then(function() {
    console.log('promise2');
});
console.log('script end');
async function a1 () {
    console.log('a1 start')
    await a2()
    console.log('a1 end')
}
async function a2 () {
    console.log('a2')
}

console.log('script start')

setTimeout(() => {
    console.log('setTimeout')
}, 0)

Promise.resolve().then(() => {
    console.log('promise1')
})

a1()

let promise2 = new Promise((resolve) => {
    resolve('promise2.then')
    console.log('promise2')
})

promise2.then((res) => {
    console.log(res)
    Promise.resolve().then(() => {
        console.log('promise3')
    })
})
console.log('script end')

@kangkai124
Copy link
Owner Author

kangkai124 commented May 30, 2019

image

注意 promise2async1 end 之前
image

image

image

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

1 participant