async function async1() {
console.log(1)
await async2()
console.log(2)
}
async function async2() {
console.log(3)
}
console.log(4)
setTimeout(function() {
console.log(5)
}, 0)
async1()
new Promise(function (resolve) {
console.log(6)
})
看到一道题,我不是特别了解 js ,我大概看了下:
为啥 6 在 2 前面呢?
1
baolongzhanshen 2023-01-13 17:47:28 +08:00
可以看一下 promise 相关的东西,大致就是 promise 构造函数中的代码是同步的。
|
2
DoubleKing 2023-01-13 17:47:38 +08:00
进入微任务的队列顺序?
|
3
tutou 2023-01-13 17:49:00 +08:00 2
await async2()
console.log(2) => Promise.resolve(async2()).then(() => { console.log(2) }) |
4
murmur 2023-01-13 17:52:18 +08:00 1
啊,这些面试题是真的害死人,你买了一把枪,然后直接照着别人脑袋砸过去,然后不解为啥人没啥事不是枪威力很大么
|
6
MossFox 2023-01-13 18:02:45 +08:00
Javascript Promises are Eager and Not Lazy: https://tusharf5.com/posts/js-promises-eager-not-lazy/
(就是一楼所说的那个) 同样的题目我记得在掘金上面看到过一个细致的题解,客户端似乎没有浏览历史记录,所以链接也没法找了。 |
7
LancerXu 2023-01-13 18:02:52 +08:00 1
await 后面的代码可以理解成.then 执行,所以被放到了微任务列表
第一轮宏任务执行输出 6 后再回头执行当前微任务列表 |
8
MossFox 2023-01-13 18:04:52 +08:00 1
|
9
zhanglintc OP |
10
zhanglintc OP @MossFox @tutou @LancerXu 各位再帮忙看下:
// 这个大概耗时 1.7 秒 function sleep() { i=0 // do a heavy job for (let j = 0; j < 1e9; j++) { i++; } console.log("sleep done") return 3 } setTimeout(function(){console.log(1)}, 10) // 队列位置 1 setTimeout(function(){console.log(2)}, 0) // 队列位置 2 sleep(); 这里认为塞入微队列的顺序应该是书写顺序吧,那么就是 1 在 2 之前。 然后 sleep 是一个耗时操作,测试大概在 1700 毫秒左右。 那么宏队列结束后调用微队列,此时先出栈 1 ,且应该已经超过 10 毫秒,那么 1 可以直接输出。 但是为什么还是先输出的 2 呢? 还是说 setTimeout 的计时是在进入微队列循环操作后才开始考虑计时器的? 期望输出: sleep done 1 2 实际输出: sleep done 2 1 |
11
MossFox 2023-01-14 20:36:53 +08:00
@zhanglintc
我不确定自己的解释对不对,所以…… 如果有路过的人发现有不准确的地方,麻烦一定要指出来一下,感谢。 setTimeOut 注册的是 marcotask (宏任务),它的行为是这样的: - 在执行到 setTimeOut 的时候,定时器会交给 JS 引擎去在指定的将来 n 毫秒的时候,将 callback function 推入宏任务队列 (**注意** 此时的任务队列是空的,等待定时器的过程不属于 JS 的任务队列里的任务,注册的回调函数才是) - 只有在当前的同步语句执行完成 (即当前的宏任务结束) 之后,宏任务队列中才会开始执行下一个任务 也就是说,即使这里在两个 setTimeOut 执行结束后,阻塞了超过 10 ms ,实际上宏任务队列也是会按照注册的时间将任务推进去的——只不过到时间了的时候,当前的宏任务还没有结束,所以回调函数不会按预期执行 (但确实是按预期的时间顺序推到了队列里面,所以是打印 2 的任务在打印 1 的任务之前被推入)。 (这整个代码块需要被视为是一整个宏任务,sleep() 执行完毕之前,宏任务队列里面的其他任务不会继续执行,但不代表宏任务队列不可以被推入新的任务。进入队列的时间不受 JS 阻塞的影响,JS 的阻塞影响的只有开始执行的时间) 啊,以防对于前面描述的宏任务的概念有些理解不到位,这里放个参考链接: https://stackoverflow.com/questions/25915634/difference-between-microtask-and-macrotask-within-an-event-loop-context |
12
MossFox 2023-01-14 20:42:33 +08:00
更正:"此时的任务队列是空的" → "此时,当前的回调函数并没有直接进入任务队列"
(正在执行的也是一个宏任务) |
13
zhanglintc OP @MossFox #11 这个字有点多,链接更多,要花点时间再理解理解。
不过我倒是试了下,如果改成这样的话 ``` setTimeout(function(){console.log(1)}, 10) // 队列位置 1 sleep(); setTimeout(function(){console.log(2)}, 0) // 队列位置 ``` 是可以输出 1 ,2 的: sleep done 1 2 |
14
zhanglintc OP @MossFox https://stackoverflow.com/a/34691484/4156036
看起来是给定的 delay 到期后,可能会有一个“事件”或者“中断”发生,然后 JS 引擎会立即处理它(也就是放到微任务)。但是这个暂时没找到资料来证明。 但是如果说 JS 引擎内部有类似的“事件”机制的话,我感觉都能解释通了。 上面的例子: setTimeout(function(){console.log(1)}, 10) setTimeout(function(){console.log(2)}, 0) 第一个 10 毫秒后触发事件,第二个立即触发事件,那么自然第二个先入队列。 到时候执行微任务的时候自然就是第二个先打印了。 |
15
tutou 2023-01-15 11:17:05 +08:00
@zhanglintc
``` setTimeout(function(){console.log(1)}, 10) // 队列位置 1 sleep(); setTimeout(function(){console.log(2)}, 0) // 队列位置 ``` 你这个 12 是 node 环境吧,你试试 chrome 环境。这东西能应对面试就行了,再研究下去就要看 v8 源码了。 补充一题 Promise.resolve().then(() => { console.log(0); return Promise.resolve(4); }).then((res) => { console.log(res) }) Promise.resolve().then(() => { console.log(1); }).then(() => { console.log(2); }).then(() => { console.log(3); }).then(() => { console.log(5); }).then(() =>{ console.log(6); }) 链接: https://juejin.cn/post/6945319439772434469#heading-31 |
16
zhanglintc OP @tutou #15 是的是 node 环境。换成 chrome 的确不一样了:
Node: sleep done 1 2 Chrome: sleep done 2 1 那意思这个 timer 的时间实际上还是跟具体实现有关是吧?不能一概而论。 |