下面这段 js 代码的输出应该是什么?

2023-01-13 17:16:13 +08:00
 zhanglintc
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 前面呢?

1027 次点击
所在节点    问与答
16 条回复
baolongzhanshen
2023-01-13 17:47:28 +08:00
可以看一下 promise 相关的东西,大致就是 promise 构造函数中的代码是同步的。
DoubleKing
2023-01-13 17:47:38 +08:00
进入微任务的队列顺序?
tutou
2023-01-13 17:49:00 +08:00
await async2()
console.log(2)

=>

Promise.resolve(async2()).then(() => { console.log(2) })
murmur
2023-01-13 17:52:18 +08:00
啊,这些面试题是真的害死人,你买了一把枪,然后直接照着别人脑袋砸过去,然后不解为啥人没啥事不是枪威力很大么
november
2023-01-13 17:58:22 +08:00
@murmur 23333 ,很喜欢这个比喻。
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/
(就是一楼所说的那个)

同样的题目我记得在掘金上面看到过一个细致的题解,客户端似乎没有浏览历史记录,所以链接也没法找了。
LancerXu
2023-01-13 18:02:52 +08:00
await 后面的代码可以理解成.then 执行,所以被放到了微任务列表
第一轮宏任务执行输出 6 后再回头执行当前微任务列表
MossFox
2023-01-13 18:04:52 +08:00
zhanglintc
2023-01-13 20:26:11 +08:00
先看了 #8 @MossFox 回复的文章,再看 #3 @tutou 和 #7 @LancerXu 的回复就全理解了。
zhanglintc
2023-01-14 17:24:00 +08:00
@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
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
MossFox
2023-01-14 20:42:33 +08:00
更正:"此时的任务队列是空的" → "此时,当前的回调函数并没有直接进入任务队列"
(正在执行的也是一个宏任务)
zhanglintc
2023-01-14 21:07:23 +08:00
@MossFox #11 这个字有点多,链接更多,要花点时间再理解理解。

不过我倒是试了下,如果改成这样的话

```
setTimeout(function(){console.log(1)}, 10) // 队列位置 1
sleep();
setTimeout(function(){console.log(2)}, 0) // 队列位置
```

是可以输出 1 ,2 的:
sleep done
1
2
zhanglintc
2023-01-14 23:57:11 +08:00
@MossFox https://stackoverflow.com/a/34691484/4156036

看起来是给定的 delay 到期后,可能会有一个“事件”或者“中断”发生,然后 JS 引擎会立即处理它(也就是放到微任务)。但是这个暂时没找到资料来证明。

但是如果说 JS 引擎内部有类似的“事件”机制的话,我感觉都能解释通了。

上面的例子:
setTimeout(function(){console.log(1)}, 10)
setTimeout(function(){console.log(2)}, 0)

第一个 10 毫秒后触发事件,第二个立即触发事件,那么自然第二个先入队列。
到时候执行微任务的时候自然就是第二个先打印了。
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
zhanglintc
2023-01-15 21:10:51 +08:00
@tutou #15 是的是 node 环境。换成 chrome 的确不一样了:

Node:
sleep done
1
2

Chrome:
sleep done
2
1

那意思这个 timer 的时间实际上还是跟具体实现有关是吧?不能一概而论。

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/908737

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX