Node.JS 记录合集

process.hrtime

process.hrtime

process.hrtime() 是一个在 Node.js 中可用的全局方法,它返回一个表示当前时间的高分辨率时间戳。时间戳表示为一个包含两个元素的数组。第一个元素是以秒为单位的时间戳,而第二个元素是以纳秒为单位的时间戳。

这个函数的特点是它可以提供非常高的时间测量精度,远超过常规的 Date.now() 或者 new Date().getTime() 这样的 JavaScript 方法。它特别适用于需要精确测量代码执行时间的情况。

它的另一个特点是它是基于启动 Node.js 进程的时间的,而不是基于 Unix 纪元(1970 年 1 月 1 日 00:00:00 UTC)的。这意味着它不受系统时间变化的影响,例如由于网络时间同步或者用户手动更改系统时间。

如果你传递一个以前 process.hrtime() 返回的时间戳作为参数给这个函数,它会返回两个时间戳之间的时间差。这对于测量代码块的执行时间非常有用。例如:

javascript
1var startTime = process.hrtime();
2// 一些需要测量执行时间的代码...
3var diffTime = process.hrtime(startTime);
4console.log(`执行时间:${diffTime[0] * 1e9 + diffTime[1]}纳秒`);

在这个例子中,diffTime[0] 是时间差的秒部分,diffTime[1] 是时间差的纳秒部分。将它们加起来得到的是以纳秒为单位的总执行时间。

async_hooks

async_hooks 是 Node.js 的一个核心模块,主要用于跟踪异步资源的生命周期。这对于诊断和分析异步行为非常有用,尤其是在复杂的、高度异步的应用程序中。以下是一些 async_hooks 的主要用途和特点:

  1. 跟踪异步操作的执行上下文:通过 async_hooks,开发者可以跟踪异步操作的创建、销毁、触发和继承等。这有助于理解异步资源(例如 Promise、回调、定时器等)如何随着应用程序的运行而改变。
  2. 诊断和调试async_hooks 可以用于构建更复杂的诊断工具,以便于识别和解决潜在的异步问题。例如,开发者可以使用它来检测内存泄漏或异步操作之间的不一致。
  3. 性能监控和分析:通过对异步操作的跟踪,可以分析它们的执行时间和频率。这有助于识别潜在的性能瓶颈或资源使用不足的区域。
  4. 上下文传播:在分布式系统或微服务体系结构中,有时需要跟踪在多个异步调用和服务之间传播的上下文。async_hooks 可以用于实现上下文传播,以确保跨服务的请求具有一致的上下文信息。
  5. 创建自定义监控和追踪工具async_hooks 可以用作构建定制的监控和追踪工具的基础,以适应特定的业务需求和应用程序特性。

下面是一个使用 async_hooks 的简单示例,用于打印异步资源的生命周期事件:

javascript
1const async_hooks = require("async_hooks");
2
3const hook = async_hooks.createHook({
4  init(asyncId, type, triggerAsyncId) {
5    console.log(`Init: ${asyncId}, type: ${type}, trigger: ${triggerAsyncId}`);
6  },
7  destroy(asyncId) {
8    console.log(`Destroy: ${asyncId}`);
9  },
10});
11
12hook.enable();

总体而言,async_hooks 是一个强大的工具,用于分析和理解异步操作在 Node.js 应用程序中的行为。不过,它还是相对低级的 API,通常用于工具和库的开发,而不是直接用于日常的应用程序开发。

cls-hooked

直接使用 async_hooks 可能会稍微复杂,特别是在大型项目中。有一些第三方库可以帮助简化此过程,例如 cls-hooked,它提供了更高级别的抽象来处理上下文本地存储。

使用 cls-hooked 来管理请求的上下文可以使代码更加简洁和直观。以下是一个使用 Express 和 cls-hooked 来跟踪每个请求 requestId 的示例。

  1. 安装所需的依赖项

    bash
    1npm install express uuid cls-hooked
  2. 编写代码

    javascript
    1const express = require("express");
    2const { v4: uuidv4 } = require("uuid");
    3const cls = require("cls-hooked");
    4
    5const app = express();
    6const port = 3000;
    7
    8// 创建名为 'request' 的命名空间
    9const requestNamespace = cls.createNamespace("request");
    10
    11// 中间件:为每个请求分配 requestId 并存储在命名空间中
    12app.use((req, res, next) => {
    13  requestNamespace.run(() => {
    14    const requestId = uuidv4(); // 生成唯一的 requestId
    15    requestNamespace.set("requestId", requestId);
    16    next();
    17  });
    18});
    19
    20app.get("/", (req, res) => {
    21  const requestId = requestNamespace.get("requestId"); // 获取当前请求的 requestId
    22  console.log(`Handling request, requestId: ${requestId}`);
    23  res.send(`Your request ID is: ${requestId}`);
    24});
    25
    26app.listen(port, () => {
    27  console.log(`Server running at http://localhost:${port}/`);
    28});
  3. 测试运行

    运行服务器并访问 http://localhost:3000/,每个请求将在控制台输出其 requestId,并在响应中返回它。

cls-hooked 通过创建命名空间并在该命名空间内运行中间件函数来实现上下文本地存储。在命名空间的上下文中,你可以使用 setget 方法来存储和检索与当前请求相关的信息。在上面的示例中,我们创建了一个名为 request 的命名空间,并在其中存储了每个请求的 requestId

这种方法的好处是它隐藏了 async_hooks 的复杂性,使你能够更容易地管理和操作请求上下文。在更大型和复杂的应用程序中,可以使用同样的模式来存储和管理跨异步操作的任何类型的上下文信息。

nodejs 中 writeFileSync 在父级目录不存在的情况下会报错

在 Node.js 中,fs.writeFileSync() 方法假定指定路径的父目录已经存在。如果父目录不存在,该方法将抛出一个错误。例如,假设您尝试写入一个文件,而其父目录尚未创建:

javascript
1const fs = require("fs");
2
3fs.writeFileSync("nonexistent_directory/file.txt", "Hello, world!");

上述代码会导致以下类型的错误:

perl
1Error: ENOENT: no such file or directory, open 'nonexistent_directory/file.txt'

要解决此问题,您可以使用 fs.mkdirSync() 方法预先创建父目录,或者使用第三方库(如 mkdirp)来递归地创建所有必要的父目录。以下是一个使用 fs.mkdirSync() 的示例:

javascript
1const fs = require("fs");
2const path = require("path");
3
4const dir = "nonexistent_directory";
5const filePath = path.join(dir, "file.txt");
6
7// 创建目录,如果它不存在
8if (!fs.existsSync(dir)) {
9  fs.mkdirSync(dir);
10}
11
12// 现在可以安全地写入文件
13fs.writeFileSync(filePath, "Hello, world!");

该方案确保了父目录存在,然后再执行 fs.writeFileSync(),从而避免了错误。需要注意的是,这个解决方案没有处理并发问题或其他潜在的文件系统相关的异常状况。在生产环境中,更全面的错误处理机制是必要的。

优雅解决办法

第三方库通常提供更高级的功能,包括在写入文件时自动创建不存在的父目录。一个流行的库是 fs-extra,它是对 Node.js 原生 fs 模块的一个扩展。

以下是使用 fs-extra 库的一个简单示例:

javascript
1const fs = require("fs-extra");
2const path = require("path");
3
4const dir = "nonexistent_directory";
5const filePath = path.join(dir, "file.txt");
6
7// 使用 ensureFile 方法确保父目录存在
8fs.ensureFile(filePath)
9  .then(() => {
10    fs.writeFileSync(filePath, "Hello, world!");
11  })
12  .catch((error) => {
13    console.error(error);
14  });

在这个示例中,ensureFile 方法会自动创建文件以及其所有不存在的父目录。一旦确保文件和目录存在,您便可以安全地使用 writeFileSync 方法写入文件。

使用这样的第三方库可以简化代码,并减少手动错误处理的需要,从而提高代码的健壮性和可维护性。

powershell 执行 node 程序假死

在使用 PowerShell 执行一个 Node 程序时遇到了假死的问题。这个程序在正常运行时会每分钟在控制台打印一个字符串,但在假死状态下不会有任何打印。当你使用 CTRL + C 退出控制台时,程序会打印出本应该输出的字符串。

可能的原因:PowerShell 控制台输出缓冲:可能存在输出缓冲问题,尤其是当输出不直接到达控制台窗口时。

解决方案:调整 PowerShell 设置:尝试关闭 PowerShell 的“快速编辑模式”,这可能会解决由于意外文本选择导致的程序暂停问题。这可以通过右击 PowerShell 窗口标题栏,选择“属性”,在“选项”标签页下取消勾选“快速编辑模式”来实现。

globalThis, global 和 window 的对比

globalThis, global, 和 window 是 JavaScript 中的全局对象,它们在不同的环境下表示不同的含义。下面是它们的主要区别:

1. globalThis

  • 环境通用性: globalThis 是一个在所有 JavaScript 环境下标准化的全局对象名称,无论是在浏览器、Node.js 还是任何其他 JavaScript 运行时环境。
  • 目的: 引入 globalThis 的目的是为了提供一个统一的全局对象,以解决在不同环境下不同全局对象名称的问题(比如浏览器中是 window,Node.js 中是 global)。
  • 兼容性: globalThis 是较新的 JavaScript 特性,因此在一些旧的浏览器或 JavaScript 环境中可能不被支持。

2. global

  • 特定环境: global 是 Node.js 中的全局对象。
  • 用途: 在 Node.js 环境中,global 类似于浏览器环境中的 window 对象。它作为全局命名空间的一部分,可以在应用程序的任何地方访问。
  • 区别: global 只存在于 Node.js 环境中,不适用于浏览器环境。

3. window

  • 特定环境: window 是浏览器环境中的全局对象。
  • 用途: 它提供了大量用于浏览器交互的方法和属性,如操作 DOM、处理事件、存储等。
  • 区别: window 对象仅在浏览器环境中存在,不适用于 Node.js 环境。

4. self

  • self 在 Web Workers 和浏览器环境中都可用。
  • 在 Web Workers 中,self 指向全局作用域,因为 Workers 没有 window 对象。
  • 在浏览器环境中,selfwindow 是相同的。

总结

  • 环境依赖性: windowglobal 是环境依赖的全局对象,分别用于浏览器和 Node.js。
  • 环境独立性: globalThis 提供了一个环境独立的方式来引用全局对象,无论在哪个 JavaScript 环境中都是一致的。
  • 向后兼容: globalThis 的引入是为了解决不同环境下全局对象命名不一致的问题,同时向后兼容旧环境。

在编写可跨多个 JavaScript 环境运行的代码时,使用 globalThis 是一个更好的实践,因为它确保了代码的一致性和可移植性。

ArrayBuffer 和 Uint8Array

ArrayBufferUint8Array 是 JavaScript 中用于处理二进制数据的两种不同类型的对象,它们在用途和特性上有所不同。

  1. ArrayBuffer

    • ArrayBuffer 对象用于表示通用的、固定长度的原始二进制数据缓冲区。
    • 你不能直接操作 ArrayBuffer 的内容;相反,你需要使用类型化数组或 DataView 来处理它的内容。
    • ArrayBuffer 是二进制数据的容器,不关心具体的数据类型。
  2. Uint8Array

    • Uint8Array 是一个类型化数组,提供了一种操作 ArrayBuffer 内容的方式。
    • 它表示一个 8 位无符号整数的数组。每个元素都是一个字节(8 位),范围是 0 到 255。
    • Uint8Array 提供了数组接口,例如访问元素、迭代等,使得操作 ArrayBuffer 变得更容易。
    • 使用 Uint8Array 可以直接读写二进制数据,每个元素都是一个字节。

总结来说,ArrayBuffer 是一个原始的二进制数据缓冲区,而 Uint8Array 是操作这些二进制数据的一种方式,专门用于处理字节数据。在需要处理二进制数据时,通常会创建一个 ArrayBuffer,然后通过 Uint8Array 或其他类型化数组来访问和操作这个缓冲区的内容。

Difference between readFile() and readFileSync()

https://stackoverflow.com/questions/17604866/difference-between-readfile-and-readfilesync

fs.readFile takes a call back which calls response.send as you have shown - good. If you simply replace that with fs.readFileSync, you need to be aware it does not take a callback so your callback which calls response.send will never get called and therefore the response will never end and it will timeout.

You need to show your readFileSync code if you're not simply replacing readFile with readFileSync.

Also, just so you're aware, you should never call readFileSync in a node express/webserver since it will tie up the single thread loop while I/O is performed. You want the node loop to process other requests until the I/O completes and your callback handling code can run.