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的事件循环机制 #39

Open
LuckyWinty opened this issue Feb 12, 2020 · 4 comments
Open

说说JS的事件循环机制 #39

LuckyWinty opened this issue Feb 12, 2020 · 4 comments
Labels

Comments

@LuckyWinty
Copy link
Owner

No description provided.

@LuckyWinty LuckyWinty added the js label Feb 12, 2020
@LuckyWinty
Copy link
Owner Author

LuckyWinty commented Feb 12, 2020

JavaScript代码的执行过程中,除了依靠函数调用栈来搞定函数的执行顺序外,还依靠任务队列(task queue)来搞定另外一些代码的执行。整个执行过程,我们称为事件循环过程。一个线程中,事件循环是唯一的,但是任务队列可以拥有多个。任务队列又分为macro-task(宏任务)与micro-task(微任务),在最新标准中,它们被分别称为task与jobs。

macro-task大概包括:

  • script(整体代码)
  • setTimeout
  • setInterval
  • setImmediate
  • I/O
  • UI render

micro-task大概包括:

  • process.nextTick
  • Promise
  • Async/Await(实际就是promise)
  • MutationObserver(html5新特性)

整体执行,我画了一个流程图:

GitHub

总的结论就是,执行宏任务,然后执行该宏任务产生的微任务,若微任务在执行过程中产生了新的微任务,则继续执行微任务,微任务执行完毕后,再回到宏任务中进行下一轮循环。举个例子:
GitHub

结合流程图理解,答案输出为:async2 end => Promise => async1 end => promise1 => promise2 => setTimeout
但是,对于async/await ,我们有个细节还要处理一下。如下:

async/await执行顺序

我们知道async隐式返回 Promise 作为结果的函数,那么可以简单理解为,await后面的函数执行完毕时,await会产生一个微任务(Promise.then是微任务)。但是我们要注意这个微任务产生的时机,它是执行完await之后,直接跳出async函数,执行其他代码(此处就是协程的运作,A暂停执行,控制权交给B)。其他代码执行完毕后,再回到async函数去执行剩下的代码,然后把await后面的代码注册到微任务队列当中。我们来看个例子:

console.log('script start')

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

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

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

console.log('script end')
// script start => async2 end => Promise => script end => promise1 => promise2 => async1 end => setTimeout

分析这段代码:

  • 执行代码,输出script start
  • 执行async1(),会调用async2(),然后输出async2 end,此时将会保留async1函数的上下文,然后跳出async1函数。
  • 遇到setTimeout,产生一个宏任务
  • 执行Promise,输出Promise。遇到then,产生第一个微任务
  • 继续执行代码,输出script end
  • 代码逻辑执行完毕(当前宏任务执行完毕),开始执行当前宏任务产生的微任务队列,输出promise1,该微任务遇到then,产生一个新的微任务
  • 执行产生的微任务,输出promise2,当前微任务队列执行完毕。执行权回到async1
  • 执行await,实际上会产生一个promise返回,即
let promise_ = new Promise((resolve,reject){ resolve(undefined)})

执行完成,执行await后面的语句,输出async1 end

  • 最后,执行下一个宏任务,即执行setTimeout,输出setTimeout

注意

新版的chrome浏览器中不是如上打印的,因为chrome优化了,await变得更快了,输出为:

// script start => async2 end => Promise => script end => async1 end => promise1 => promise2 => setTimeout

但是这种做法其实是违法了规范的,当然规范也是可以更改的,这是 V8 团队的一个 PR ,目前新版打印已经修改。
知乎上也有相关讨论,可以看看 https://www.zhihu.com/question/268007969

我们可以分2种情况来理解:

  1. 如果await 后面直接跟的为一个变量,比如:await 1;这种情况的话相当于直接把await后面的代码注册为一个微任务,可以简单理解为promise.then(await下面的代码)。然后跳出async1函数,执行其他代码,当遇到promise函数的时候,会注册promise.then()函数到微任务队列,注意此时微任务队列里面已经存在await后面的微任务。所以这种情况会先执行await后面的代码(async1 end),再执行async1函数后面注册的微任务代码(promise1,promise2)。

  2. 如果await后面跟的是一个异步函数的调用,比如上面的代码,将代码改成这样:

console.log('script start')

async function async1() {
await async2()
console.log('async1 end')
}
async function async2() {
console.log('async2 end')
return Promise.resolve().then(()=>{
  console.log('async2 end1')
})
}
async1()

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

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

console.log('script end')

输出为:

// script start => async2 end => Promise => script end => => promise1 => promise2 => async1 end setTimeout

此时执行完awit并不先把await后面的代码注册到微任务队列中去,而是执行完await之后,直接跳出async1函数,执行其他代码。然后遇到promise的时候,把promise.then注册为微任务。其他代码执行完毕后,需要回到async1函数去执行剩下的代码,然后把await后面的代码注册到微任务队列当中,注意此时微任务队列中是有之前注册的微任务的。所以这种情况会先执行async1函数之外的微任务(promise1,promise2),然后才执行async1内注册的微任务(async1 end).
可以理解为,这种情况下,await 后面的代码会在本轮循环的最后被执行.

@shen-zhao
Copy link

shen-zhao commented Feb 21, 2020

对于后面两个案例我是这样的理解@LuckyWinty

首先讨论讨论一下async函数的返回值,我们都知道是一个新的promise对象,而不是真正函数体里return的那个值,但是不同值的类型会影响返回promise对象的行为,分两种情况:

1.返回一个非promise类型的任何值,此时返回的promise对象会把返回值作为作为resolve的结果,根据案例结果,这个promise立即被加入到了微任务队列

Promise.resolve(value);

2.返回一个promise类型的值,此时返回的promise对象会继承返回值的状态和值,类似于promise的链式调用,当链条中上游的promise的回调没有执行(执行时会resolve下游的promise),下游的promise还是pending状态!上游执行后,下游promise任务会追加到微任务队列尾部
根据案例中的结果,这个promise没有立即添加到微任务队列中,是因为它所依赖的promise回调还没有执行(异步)

参照以下代码:

Promise.resolve().then(() => {
  console.log('promise 1');
}).then(() => {
  console.log('promise 2');
});

Promise.resolve().then(() => {
  console.log('promise 3');
});
// promise 1 => promise 3 => promise 2

个人观点:
上述async/await的不同行为都可以转成promise理解
(说的有问题大家指正!!!)

@LuckyWinty
Copy link
Owner Author

对于后面两个案例我是这样的理解@LuckyWinty

首先讨论讨论一下async函数的返回值,我们都知道是一个新的promise对象,而不是真正函数体里return的那个值,但是不同值的类型会影响返回promise对象的行为,分两种情况:

1.返回一个非promise类型的任何值,此时返回的promise对象会把返回值作为作为resolve的结果,根据案例结果,这个promise立即被加入到了微任务队列

Promise.resolve(value);

2.返回一个promise类型的值,此时返回的promise对象会继承返回值的状态和值,类似于promise的链式调用,当链条中上游的promise的回调没有执行(执行时会resolve下游的promise),下游的promise还是pending状态!上游执行后,下游promise任务会追加到微任务队列尾部
根据案例中的结果,这个promise没有立即添加到微任务队列中,是因为它所依赖的promise回调还没有执行(异步)

参照以下代码:

Promise.resolve().then(() => {
  console.log('promise 1');
}).then(() => {
  console.log('promise 2');
});

Promise.resolve().then(() => {
  console.log('promise 3');
});
// promise 1 => promise 3 => promise 2

个人观点:
上述async/await的不同行为都可以转成promise理解
(说的有问题大家指正!!!)

也可以吧,就是下游promise任务会追加到微任务队列尾部,感觉怪怪的,队列只有入队吧,没有直接加尾部一说吧

@sfsoul
Copy link

sfsoul commented May 24, 2020

对于后面两个案例我是这样的理解@LuckyWinty

首先讨论讨论一下async函数的返回值,我们都知道是一个新的promise对象,而不是真正函数体里return的那个值,但是不同值的类型会影响返回promise对象的行为,分两种情况:

1.返回一个非promise类型的任何值,此时返回的promise对象会把返回值作为作为resolve的结果,根据案例结果,这个promise立即被加入到了微任务队列

Promise.resolve(value);

2.返回一个promise类型的值,此时返回的promise对象会继承返回值的状态和值,类似于promise的链式调用,当链条中上游的promise的回调没有执行(执行时会resolve下游的promise),下游的promise还是pending状态!上游执行后,下游promise任务会追加到微任务队列尾部
根据案例中的结果,这个promise没有立即添加到微任务队列中,是因为它所依赖的promise回调还没有执行(异步)

参照以下代码:

Promise.resolve().then(() => {
  console.log('promise 1');
}).then(() => {
  console.log('promise 2');
});

Promise.resolve().then(() => {
  console.log('promise 3');
});
// promise 1 => promise 3 => promise 2

个人观点:
上述async/await的不同行为都可以转成promise理解
(说的有问题大家指正!!!)

你理解的很对啊~

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

3 participants