分享一道 Node.js 的面试题,考察 JS 相关的最基本的掌握程度,代码完全来自官方文档

1 月 15 日
 superhot

简述以下代码的输出结果,并解释执行过程:

import { once, EventEmitter } from 'node:events';
import process from 'node:process';

const ee = new EventEmitter();

process.nextTick(() => {
  ee.emit('myevent', 42);
});

const [value] = await once(ee, 'myevent');
console.log(value);

const err = new Error('kaboom');
process.nextTick(() => {
  ee.emit('error', err);
});

try {
  await once(ee, 'myevent');
} catch (err) {
  console.error('error happened', err);
}

以上代码来自这里

里面涉及到的基础知识点有:

  1. Promise
  2. async/await
  3. Node.js 中的微任务队列与 nexttick 队列
  4. CJS 与 MJS 的差异
  5. Node.js 中的 EventEmitter

最后,这真的不算八股,AI 当然可以解释清楚,但这么一段简单清晰的代码,你还不知所以然的话,那对着 Vibe Coding 出来的屎山,最后只能束手无策了。

3201 次点击
所在节点    JavaScript
23 条回复
Zhuzhuchenyan
1 月 15 日
哈哈,这不是几年前我校招的时候面试官最“喜欢”的面试题嘛

看我随手出一道,console.log 1234 才是味道最足的

console.log(1);
setTimeout(() => console.log(2), 0);
process.nextTick(() => {
console.log(3);
queueMicrotask(() => console.log(4));
});

queueMicrotask(() => {
console.log(5);
process.nextTick(() => console.log(6));
});
new Promise(resolve => {
console.log(7);
resolve();
console.log(8);
}).then(() => {
console.log(9);
process.nextTick(() => console.log(10));
queueMicrotask(() => console.log(11));
});

new Promise(resolve => {
console.log(12);
setTimeout(() => {
console.log(13);
resolve();
}, 0);
}).then(() => {
console.log(14);
});

(async () => {
console.log(15);
await void 0;
console.log(16);
process.nextTick(() => console.log(17));
queueMicrotask(() => console.log(18));
})();

console.log(19);
craftsmanship
1 月 15 日
@Zhuzhuchenyan 🆘啊
tinx
1 月 15 日
AI 既然清清楚楚,那怎么会堆出类似错误的屎山
tearnarry
1 月 15 日
既然 AI 可以解释清楚
1. 不会 Vibe Coding 出来屎山
2. 如果有 Vibe Coding 出来的屎山,继续让 AI 修复即可
4seasons
1 月 15 日
@Zhuzhuchenyan 这个玩意儿我寻思着真的有人能分析出来吗?
lanced
1 月 15 日
@Zhuzhuchenyan 太经典了
wuxilaoshiren
1 月 15 日
典典典 前些年面试就喜欢问这个
但是随着 AI 的到来,感觉看这些都没意义了
ssssiiiirren
1 月 15 日
哥们你这题目出的有问题,你这程序但凡要能运行的下去,就必须先打印 42 ,然后后面就能推出来了。
如果不先打印 42 ,await 那里就卡住了。
chenluo0429
1 月 15 日
我面试的时候从来不会问这些。一个合格的开发应当知道一些会引发典型的回调/异步等时序难以确定的场景,然后在正常的开发过程中,别这么用他们
Ketteiron
1 月 15 日
我说个暴论,凡是用得上 EventEmitter 的项目,99%是屎山
SanjinGG
1 月 15 日
能出现这种代码的公司还有必要去?
woodytang
1 月 15 日
import { once, EventEmitter } from 'node:events';
import process from 'node:process';
//定义一个 事件喇叭
const ee = new EventEmitter();

process.nextTick(() => {
//在异步队列里 喇叭发消息
ee.emit('myevent', 42);
});

// 只处理一次的监听器,在主流程执行完后,会听到这个消息
const [value] = await once(ee, 'myevent');
console.log(value);

const err = new Error('kaboom');
process.nextTick(() => {
// 在异步队列里 喇叭发消息,但这次发的是一个 nodejs bug 设计,'error'是 nodejs hardcode 的 key ,你发这个消息相当于抛异常
ee.emit('error', err);
});

try {
//虽然你没有监听'error', 但是 nodejs 内部强迫你监听了
await once(ee, 'myevent');
} catch (err) {
//虽然只是发了个消息,但是确抛了个异常
console.error('error happened', err);
}



这个是大傻逼设计,消息是消息,异常是异常,违反 solid 原则,后来的 bunjs 运行时 不鼓励使用这种方式控制流程,
一般会使用 promise ,在异步方法里 throw 异常,然后使用 Controller ,控制异步任务的退出,也可以。

另外 现代化运行时框架 认为,用事件做控制流是反模式 是 anti pattern ,会把代码搞得很乱,不可追溯,一般采用 回调 来响应事件,更符合函数式编程


这样可以过面试吗?
superhot
1 月 15 日
@Zhuzhuchenyan 太可怕了……

@lqm
@tearnarry 只要 AI 不能做到 100% 准确,就需要最终由人来把关,前提是你真的有能力做到这点。

@wuxilaoshiren 确实没必要纠结八股,但基础还是要有的。

@ssssiiiirren 顺序也许好推,但解释原理呢?

@chenluo0429 也许有更好的方式考察这些基础,想问一下都有哪些“会引发典型的回调/异步等时序难以确定的场景”

@Ketteiron 为什么呢?我的理解是 Node 中的很多类都基于 EventEmitter ,比如 Stream ,所以是很有必要去了解的。

@woodytang 受教了,“用事件做控制流是反模式 是 anti pattern ,会把代码搞得很乱,不可追溯,一般采用 回调 来响应事件,更符合函数式编程”,可以再深入解释一下这段话吗?回调响应事件,不就是 emitter.on 吗?
woodytang
1 月 15 日
@superhot

事件是这样的,emit("xxx",'咕咕咕'), listen('xxx',(text)=>{console.log("通知":text)})
回调是这样的
async fn(handle){
handle('咕咕咕')
}

handle(text){
console.log("通知":text)
}

await fn(handle)

--------

它们本质都是 解决异步情况下,也就是在不确定什么时候的情况下,A 给 B 发消息的问题,,
事件是发出去不管的,它和监听者没有绑定关系

回调是是绑定的,可以追踪,可以测试的

但是事件可以批量订阅,你要发广播可以使用事件,

如果你只是要链式执行,使用回调
songray
1 月 16 日
EventEmitter 和 process 都是 node 独有的,所以这个问题与其说是考察 JS 相关,不如说是考察 Node.js 实现...

更不用说 Node 项目八百年都用不上这些玩意。

浏览器环境下的事件循环就那几个 API ,根本没这么麻烦。

--------------

再提一点,当初 Ryan 搞出 node:events 纯粹是因为当时的 JS 还没有 Promise ,observable ,stream 。只有回调、setTimeout 和 polling 。

EventEmitter 就是蛮荒时代不成熟的造物而已,现在面试还问这个,跟问 IE 浏览器兼容没什么区别,怕不是喝大了。
superhot
1 月 16 日
@songray 看大家的说法,似乎现在没必要再去了解 EventEmitter 了?
GiantHard
1 月 16 日
> 最后,这真的不算八股,AI 当然可以解释清楚,但这么一段简单清晰的代码,你还不知所以然的话,那对着 Vibe Coding 出来的屎山,最后只能束手无策了。

确实不算八股,这算 NodeJS 实现细节;但既然 AI 可以解释清楚,为啥还会不知所以然?

我的一个感受是,现在 LLM 的知识广度已经远远超过人类了,考察一个人是否了解一项技术细节意义确实没以前那么重要。

> 只要 AI 不能做到 100% 准确,就需要最终由人来把关,前提是你真的有能力做到这点。

人也做不到 100% 准确,要不然就不会有 QA 团队了。之所以我们需要人类开发者,是因为人类开发者拥有一些 LLM 不具备的优势。

我觉得的人相对于 AI 的一个优势就是读不懂一些晦涩的代码,现在 LLM 读混淆后的 JS 都能把业务逻辑还原得八九不离十,这对于绝大多数人类来说是非常艰巨的任务。但也正是因为这点,人厌恶读起来不舒服的代码,在代码出现坏味道的时候,人的潜意识中就会产生抗拒情绪,这种情绪又会反过来让人避免编写有坏味道的代码:

> 一个合格的开发应当知道一些会引发典型的回调/异步等时序难以确定的场景,然后在正常的开发过程中,别这么用他们

因此,只要 AI 生成的代码还需要人类参与维护,就需要有品味好的开发者充当 AI 代码的质检员,要么拒绝晦涩的代码进入代码库,要么在代码库出现坏味道的时候,能够自己动手或者指挥 AI 去重构、重写。
wangtian2020
1 月 16 日
setTimeout(() => {
console.log('a')
setTimeout(() => console.log('b'), 1)
}, 1)
setTimeout(() => {
console.log('c')
}, 15)
superhot
1 月 16 日
@GiantHard 我个人理解是,只要 AI 无法消除幻觉,做到 100% 准确,就始终需要使用者具备判断 AI 是否准确的能力,而非照单全收。在这个例子里面,能断定 AI 可以解释清楚的前提是,你能完全理解以上提及的知识点。否则 AI 自圆其说,也许会把你说服,但却是在扯谎,那就非常糟糕了。

> 我的一个感受是,现在 LLM 的知识广度已经远远超过人类了,考察一个人是否了解一项技术细节意义确实没以前那么重要。

说实话,确实如此,但还是会有所顾虑,无法做到完全信任。另一方面,在 AI 的知识深度与广度都远超人类的今天,我们这些技术人员应该把精力放在哪里呢?有些迷茫。
KisekiRemi
1 月 16 日
典中典,麻烦 OP 报一下你公司名,以后避开你司合作

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

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

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

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

© 2021 V2EX