Node.js 面试高频问题解析:回调、Promise 与时序攻击

作者:慢半拍时间:2026-10-22 00:40:03浏览:199

本文深入解析 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)?

  1. 采用模块化设计,将复杂的回调逻辑拆分为独立的函数,提高代码可读性。
  2. 使用控制流库,例如 async 库,来管理异步操作的顺序和依赖。
  3. 结合 Generator 函数与 Promise,实现更灵活的异步流程控制。
  4. 使用 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 响应头时,应添加以下关键属性:

  1. HttpOnly:该属性旨在防止跨站脚本攻击。设置此属性后,Cookie 将不允许被 JavaScript 代码读取,从而降低 XSS 攻击窃取 Cookie 的风险。
  2. 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 等知名应用曾存在此类漏洞。

Error-first 回调机制

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) 
  1. 首先创建一个新的 Promise,它解析参数 1。
  2. 解析后的值被加上 1(结果变为 2),并立即返回这个 2。
  3. 此时解析的值被丢弃,代码抛出一个异常。
  4. 该异常被后续的 catch 块捕获并丢弃(或处理),新的值 1 被返回(注意:这里原文逻辑描述可能有误,通常 catch 后返回的值会进入下一个 then,但根据原文 t35 描述“新的值1被返回”,我们保留原文表述逻辑,即 catch 内部逻辑或上下文导致的返回值)。
  5. catch 块执行完毕后,流程继续。在异常处理之后,代码继续运行,一个新的、增加了 1 后的值 2 被返回(此处依据原文 t36 描述,即 catch 后可能还有逻辑或返回默认值,原文指出“在异常处理之前,它继续运行...新的值2被返回”,结合 t35 的“1被返回”可能存在原文逻辑跳跃,但需忠实改写原文:catch 后运行不会停止,在异常处理之前,它继续运行,一个新的,增加了1后的值2被返回)。
  6. 最终,返回值被打印出来。
  7. 最后一行 catch 不会运行,因为在此之前的链中已经通过 return 1 或类似逻辑处理了异常路径,没有未处理的异常抛出。

总结

以上就是关于 Node.js 的几道面试题解析,希望本文的内容对大家能有所帮助。如果有疑问,欢迎留言交流,感谢大家对本站的支持。

作者最新文章