本文深入解析 Node.js 常见面试题,涵盖 Error-first 回调机制、避免回调地狱的多种方案(模块化、async 库、Generator、async/await),以及 Promise 链式调用的错误处理。重点讲解 HTTP Cookie 的 HttpOnly 与 Secure 属性,并详细剖析时序攻击原理及 cryptiles 库的防御方案,最后通过代码示例解析 Promise 执行流程。
Error-first 回调机制
什么是 Error-first 回调方式?
Error-first 回调模式用于同时传递错误信息和数据。该模式要求将错误对象作为回调函数的第一个参数,因此调用者必须首先检查该参数以确认是否存在错误。后续的参数则用于传递实际的业务数据。
fs.readFile(filePath, function(err, data) {
if(err) {
//处理错误,这里的return很重要,如果发生错误,在此处就会停止了。
return console.log(err);
}
//传递data
console.log(data);
})
避免回调地狱的方案
如何避免回调地狱(Callback Hell)?
- 采用模块化设计,将复杂的回调逻辑拆分为独立的函数,提高代码可读性。
- 使用控制流库,例如 async 库,来管理异步操作的顺序和依赖。
- 结合 Generator 函数与 Promise,实现更灵活的异步流程控制。
- 使用 async/await 语法,使异步代码看起来像同步代码,提升可维护性。
Promise 基础概念
Promise 是什么?
Promise 是一种用于处理异步操作的对象。它提供了一种更清晰、更强大的机制来管理异步流程,避免了传统回调方式带来的嵌套过深问题。
new Promise((resolve, reject) => {
setTimeout(() => {
resolve('result');
}, 100)
})
.then(console.log)
.catch(console.error)
Stub 在测试中的应用
Stub 是什么?能否举例说明?
Stub(桩模块)是一种用于模拟组件或模块行为的测试工具。在单元测试阶段,它能够为特定的函数调用提供预设的内部响应,从而隔离外部依赖。
例如,在测试文件写入功能时,可以使用 Stub 模拟 fs.writeFile 的行为,实际并不执行磁盘写入操作,而是直接调用回调函数返回成功状态。
var fs = require('fs');
var writeFileStub = sinon.stub(fs, 'writeFile', function(path, data, cb) {
return cb(null)
})
expect(writeFileStub).to.be.called
writeFileStub.restore();
HTTP Cookie 安全配置
如何确保 HTTP Cookie 的安全性,防止跨站脚本攻击(XSS)?
在设置 Set-Cookie HTTP 响应头时,应添加以下关键属性:
- HttpOnly:该属性旨在防止跨站脚本攻击。设置此属性后,Cookie 将不允许被 JavaScript 代码读取,从而降低 XSS 攻击窃取 Cookie 的风险。
- Secure:该属性指示浏览器仅在通过 HTTPS 加密连接时才发送该 Cookie,确保 Cookie 在传输过程中不被窃听。
配置示例如下:Set-Cookit: sid=<cookit-value>; HttpOnly
Promise 错误处理与调试
分析以下代码存在的问题
new Promise((resolve, reject) => {
throw new Error('error')
}).then(console.log)
代码中 Promise 的 then 链末尾未挂载 catch 处理器。如果 Promise 内部发生错误,该错误将未被捕获,导致程序静默失败,开发者无法得知具体的错误位置,且可能引发未处理的 Promise 拒绝(Unhandled Rejection)。
修改后的代码应如下所示:
new Promise((resolve, reject) => {
throw new Error('error')
}).then(console.log).catch(console.error)
在调试大型项目时,若难以定位哪个 Promise 出现异常,可以注册 unhandledRejection 事件监听器。该监听器会打印所有未被 catch 处理的 Promise 异常,辅助调试。
process.on('unhandledRejection', (err) => {
console.log(err)
})
时序攻击原理与防御
分析以下代码存在的问题
function checkApiKey(apiKeyFromDb, apiKeyReceived) {
if (apiKeyFromDb === apiKeyReceived) {
return true
}
return false
}
乍看之下,这只是一个普通的 if-else 条件判断代码,似乎没有明显问题。
然而,这段代码用于比较安全密钥(如 API Key)。在安全敏感场景下,不能泄露任何关于比较过程的额外信息,因此必须确保比较操作在恒定时间内完成。否则,应用可能遭受时序攻击(Timing Attack)。
什么是时序攻击(Timing Attacks)?Node.js 使用的 V8 引擎在比较字符串时,可能会进行逐字符的优化:一旦发现字符不匹配,便立即停止比较并返回结果。这种执行时间的差异可能被攻击者利用。
可以使用 cryptiles 这个 npm 模块来解决此问题,该模块提供了恒定时间比较函数。
function checkApiKey(apiKeyFromDb, apiKeyReceived) {
return cryptiles.fixedTimeCimparison(apiKeyFromDb, apiKeyReceived)
}
如何通俗地解释时序攻击(Timing Attack)?
时序攻击属于侧信道攻击(Side Channel Attack,也称旁路攻击)。侧信道攻击是指利用信道外的物理或逻辑信息(如加解密速度、芯片引脚电压、密文传输流量等)进行攻击的方式,其核心思想是“旁敲侧击”。
举一个最简单的计时攻击例子:假设某个函数负责比较用户输入的密码与系统存储的密码。如果该函数从第一位开始比较,发现不同立即返回,攻击者可以通过测量响应时间的差异,推断出密码中哪一位开始不匹配。这使得暴力破解的复杂度呈指数级下降,从成千上万倍甚至百万千万倍的难度大幅降低。
最简单的防御方法是:在发现字符不匹配时不立即返回,而是设置一个标志位,继续完成整个字符串的比较,最后再根据标志位返回结果。
时序攻击并非纯理论,OpenSSL、OpenSSH 等知名应用曾存在此类漏洞。

Promise 执行流程解析
以下代码的输出结果是什么?
Promise.reso(1)
.then((x) => x + 1)
.then((x) => {throw new Error('My Error')})
.catch(() => 1)
.then((x) => x + 1)
.then((x) => console.log(x))
.catch(console.error)
- 首先创建一个新的 Promise,它解析参数 1。
- 解析后的值被加上 1(结果变为 2),并立即返回这个 2。
- 此时解析的值被丢弃,代码抛出一个异常。
- 该异常被后续的 catch 块捕获并丢弃(或处理),新的值 1 被返回(注意:这里原文逻辑描述可能有误,通常 catch 后返回的值会进入下一个 then,但根据原文 t35 描述“新的值1被返回”,我们保留原文表述逻辑,即 catch 内部逻辑或上下文导致的返回值)。
- catch 块执行完毕后,流程继续。在异常处理之后,代码继续运行,一个新的、增加了 1 后的值 2 被返回(此处依据原文 t36 描述,即 catch 后可能还有逻辑或返回默认值,原文指出“在异常处理之前,它继续运行...新的值2被返回”,结合 t35 的“1被返回”可能存在原文逻辑跳跃,但需忠实改写原文:catch 后运行不会停止,在异常处理之前,它继续运行,一个新的,增加了1后的值2被返回)。
- 最终,返回值被打印出来。
- 最后一行 catch 不会运行,因为在此之前的链中已经通过 return 1 或类似逻辑处理了异常路径,没有未处理的异常抛出。
总结
以上就是关于 Node.js 的几道面试题解析,希望本文的内容对大家能有所帮助。如果有疑问,欢迎留言交流,感谢大家对本站的支持。