Bun技术评估 - 25 Utils(实用工具)

概述

本文是笔者的系列博文 《Bun技术评估》 中的第二十五篇。

在本文的内容中,笔者主要想要来探讨一下Bun中的一些零碎的,实用的功能,Utils(实用工具)。

同样,在Bun技术文档中,有一个相关的章节:

bun.com/docs/api/ut...

笔者初步的研究了一下,发现了很多有趣的东西。很多内容其实也不复杂,但非常实用,可以解决一些以前在nodejs中可以做,但好像又不那么顺手轻松的问题。

Sleep(休眠)

要在nodejs中,要实现模拟一个休眠的功能,恐怕要搬出Promise吧?

在Bun中,非常简单,有直接可用的方法:

js 复制代码
// await调用
console.log("hello");
await Bun.sleep(1000);
console.log("hello one second later!");


// 同步方法
Bun.sleepSync(1000); // blocks thread for one second

// 休眠到时间点
const oneSecondInFuture = new Date(Date.now() + 1000);

console.log("hello");
await Bun.sleep(oneSecondInFuture);
console.log("hello one second later!");

sleep的默认参数,和setTimeOut一样,单位是ms。而且sleep还可以支持到休眠到未来某个时间点的功能。

zip(压缩)

Bun内置了数据压缩和解压的功能,使用起来也非常方便:

js 复制代码
// 压缩内容,转换成buffer
const buf = Buffer.from("hello".repeat(100)); // Buffer extends Uint8Array

// 压缩数据
const compressed = Bun.gzipSync(buf);

buf; // => Uint8Array(500)
compressed; // => Uint8Array(30)

// 解压缩
const dec = new TextDecoder();
const uncompressed = Bun.gunzipSync(compressed);
dec.decode(uncompressed);

Bun压缩通常使用gzip算法(zlib库提供),也支持zlib的选项(ZlibCompressionOptions),作为第二个参数提供,如:

js 复制代码
{
    level: (-1~9), // 压缩级别
    memLevel: (1~9), // 内存级别
    windowBits: //窗口比特
    strategy: //策略
}

关于这些选项的详细信息,可以参考技术文档。

除了gzip,也可以直接使用deflate/inflate算法:

js 复制代码
// 压缩
const buf = Buffer.from("hello".repeat(100));
const compressed = Bun.deflateSync(buf);

buf; // => Uint8Array(500)
compressed; // => Uint8Array(12)

// 解压缩
const dec = new TextDecoder();
const decompressed = Bun.inflateSync(compressed);
dec.decode(decompressed);
// => "hellohellohello..."

我们可以看到在这里deflate的压缩率是比gzip要高的。笔者经过查阅相关资料,了解到其实gzip是一种封装格式,它本身也使用deflate压缩算法,但增加了文件头和校验机制(CRC32)。

inspect(检查器)

检查器,是一种序列化方法,它可以将对象的内容转换成字符串,可以用于console输出,如果没有这个,可能输出的内容是"[object]" 这种形式。

js 复制代码
// 一般对象
const obj = { foo: "bar" };
const str = Bun.inspect(obj);
// => '{\nfoo: "bar" \n}'

const arr = new Uint8Array([1, 2, 3]);
const str = Bun.inspect(arr);
// => "Uint8Array(3) [ 1, 2, 3 ]"


// 表格形式,带样式
console.log(
  Bun.inspect.table(
    [
      { a: 1, b: 2, c: 3 },
      { a: 4, b: 5, c: 6 },
    ],
    {
      colors: true,
    },
  ),
);

// 自定义默认输出
class Foo {
  [Bun.inspect.custom]() {
    return "bar";
  }
}

const foo = new Foo();
console.log(foo); // => "bar"

笔者觉得,和JSON.stringify相比,inspect提供了更容易阅读的格式,更加美观了,其他似乎差别不大。

此外,inspect还有一个很实用的自定义输出内容的特性(inspect.custom),类似于java程序中,重写toString方法。使用这个方法,开发者可以修改并自定义默认的输出形式。

version(版本)

程序中,可以获取Bun的版本:

js 复制代码
console.log(Bun.version,Bun.revision);

1.2.18 
0d4089ea7c48d339e87cc48f1871aeee745d8112

除了常规意义上的版本,还提供了一个revision(修订版),这是一个更确切的技术化的版本代码,用一个哈希标识来表示。

evn(环境)

本质上就是process.env(别名)。笔者在前面的文章中已经探讨过了。

Main

指当前程序的主脚本文件,应当就是入口文件:

js 复制代码
// 入口文件
console.log(Bun.main);

// 判断当前文件是否入口,或者作为模块
if (import.meta.path === Bun.main) {
  // this script is being directly executed
} else {
  // this file is being imported from another script
}

笔者觉得这个特性最常用的场景,就是用来判断当前的文件是否就是主程序,或者是作为模块被引用,从而进行不同的处理。

which

类似shell的which命令,获得一个可执行命令的位置所在。

js 复制代码
const c = Bun.which("bun");
console.log(c);

C:\Tools\bin\bun.exe

// 可配置Path
const ls = Bun.which("ls", {
  PATH: "/usr/local/bin:/usr/bin:/bin",
});
console.log(ls); // "/usr/bin/ls"

randomUUIDv7

内置的UUID生成器,第七版。这应该是现在比较推荐使用的UUID生成方法,它优化了很多时间编码和随机数生成方法。

js 复制代码
import { randomUUIDv7 } from "bun";

const id = randomUUIDv7();
// => "0192ce11-26d5-7dc3-9305-1426de888c5a"

// buffer形式
const buffer = Bun.randomUUIDv7("buffer");


// 定义
namespace Bun {
  function randomUUIDv7(
    encoding?: "hex" | "base64" | "base64url" = "hex",
    timestamp?: number = Date.now(),
  ): string;
  /**
   * If you pass "buffer", you get a 16-byte buffer instead of a string.
   */
  function randomUUIDv7(
    encoding: "buffer",
    timestamp?: number = Date.now(),
  ): Buffer;

  // If you only pass a timestamp, you get a hex string
  function randomUUIDv7(timestamp?: number = Date.now()): string;
}

peek(速览)

peek可以用于获取Promise的结果。它不需要使用await或者then,可能在某些场景中更加方便和灵活。

js 复制代码
import { peek } from "bun";

const promise = Promise.resolve("hi");

// no await!
const result = peek(promise);
console.log(result); // "hi"

和await执行模式不同,peek方法,是可以重复调用的,而且peek也支持非Promise对象:

js 复制代码
import { peek } from "bun";
import { expect, test } from "bun:test";

test("peek", () => {
  const promise = Promise.resolve(true);

  // no await necessary!
  expect(peek(promise)).toBe(true);

  // if we peek again, it returns the same value
  const again = peek(promise);
  expect(again).toBe(true);

  // if we peek a non-promise, it returns the value
  const value = peek(42);
  expect(value).toBe(42);

  // if we peek a pending promise, it returns the promise again
  const pending = new Promise(() => {});
  expect(peek(pending)).toBe(pending);

  // If we peek a rejected promise, it:
  // - returns the error
  // - does not mark the promise as handled
  const rejected = Promise.reject(
    new Error("Successfully tested promise rejection"),
  );
  expect(peek(rejected).message).toBe("Successfully tested promise rejection");
});

使用peek,还可以获取Promise对象的状态:

js 复制代码
// 判断Promise状态
import { peek } from "bun";
import { expect, test } from "bun:test";

test("peek.status", () => {
  const promise = Promise.resolve(true);
  expect(peek.status(promise)).toBe("fulfilled");

  const pending = new Promise(() => {});
  expect(peek.status(pending)).toBe("pending");

  const rejected = Promise.reject(new Error("oh nooo"));
  expect(peek.status(rejected)).toBe("rejected");
});

openInEditor

此方法可以调用编辑器打开脚本或者文件。

js 复制代码
// 使用默认编辑器
const currentFile = import.meta.url;
Bun.openInEditor(currentFile);

// 编辑器设置 bunfig.toml
[debug]
editor = "code"

// 指定编辑器和设置
Bun.openInEditor(import.meta.url, {
  editor: "vscode", // or "subl"
  line: 10,
  column: 5,
});

不知道是否能够用于开启任意文件呢。

deepEuqals(深度相等)

判断两个对象,是否严格相等,就是所有的属性和值都相等。还可以选择是否使用"严格模式",就是不忽略undefined值或者null值。

js 复制代码
const foo = { a: 1, b: 2, c: { d: 3 } };

// true
Bun.deepEquals(foo, { a: 1, b: 2, c: { d: 3 }});

// false
Bun.deepEquals(foo, { a: 1, b: 2, c: { d: 4 }});

// 严格模式
const a = { entries: [1, 2] };
const b = { entries: [1, 2], extra: undefined };

Bun.deepEquals(a, b); // => true
Bun.deepEquals(a, b, true); // => false

在内置测试方法 expect().toEqual() 中,就是使用deepEquals。expect().toStrictEqual()使用的也是它的严格模式。

escapeHTML(HTML转义)

使用HTML的规范,将一些字符进行转义。例如:

字符 转义
" &quot
& &amp
' &#x27
< &lt
/> &gt

在Bun中,这些转义操作是经过优化的,可以高效的在很大的文本流中进行处理。

stringWidth(字符宽度)

用于计算字符串在终端中显示的宽度。这个功能经常用于字符界面对齐,检查字符串是否包括ANSI转义代码,或者测量字符串在终端界面中的宽度等。

js 复制代码
Bun.stringWidth("hello"); // => 5
Bun.stringWidth("\u001b[31mhello\u001b[0m"); // => 5
Bun.stringWidth("\u001b[31mhello\u001b[0m", { countAnsiEscapeCodes: true }); // => 12

有一个string-width的npm包,也可以完成类似的功能,但bun的方法性能高出了数千倍。

fileURL

Bun内置提供了文件URL的相关处理和转换功能:

js 复制代码
// URL转path
const path = Bun.fileURLToPath(new URL("file:///foo/bar.txt"));
console.log(path); // "/foo/bar.txt"

// path转URL
const url = Bun.pathToFileURL("/foo/bar.txt");
console.log(url); // "file:///foo/bar.txt"

nanoseconds(纳秒)

如果需要非常高的时间精度,可能需要使用纳秒方法。

js 复制代码
Bun.nanoseconds();
// => 7288958

ReadableStreamToXXX(读取流)

bun内置了一系列读取流相关转换的方法,可以直接使用:

js 复制代码
const stream = (await fetch("https://bun.com")).body;
stream; // => ReadableStream

await Bun.readableStreamToArrayBuffer(stream);
// => ArrayBuffer

await Bun.readableStreamToBytes(stream);
// => Uint8Array

await Bun.readableStreamToBlob(stream);
// => Blob

await Bun.readableStreamToJSON(stream);
// => object

await Bun.readableStreamToText(stream);
// => string

// returns all chunks as an array
await Bun.readableStreamToArray(stream);
// => unknown[]

// returns all chunks as a FormData object (encoded as x-www-form-urlencoded)
await Bun.readableStreamToFormData(stream);

// returns all chunks as a FormData object (encoded as multipart/form-data)
await Bun.readableStreamToFormData(stream, multipartFormBoundary);

这些功能方法非常强大灵活,可以将流读取成为ArrayBuffer、Bytes、Blob、Chunks、Text、JSON、Form、FormData等等,几乎包括了所有可能的形式。

resolveSync(同步路径解析)

文件路径解析转换是一个非常常见的需求,bun可以在不使用fs/path模块的情况下,进行路径的解析和合并,而且是同步的。

js 复制代码
Bun.resolveSync("./foo.ts", "/path/to/project");
// => "/path/to/project/foo.ts"

Bun.resolveSync("zod", "/path/to/project");
// => "/path/to/project/node_modules/zod/index.ts"

serialize(序列化)

bun内置的jsc模块,提供了更高效的序列化和反序列化方法,可以将JavaScript对象,转换成为ArrayBuffer,并且进行反向转换。这显然是比stringify更为高效的转换方式!?应当优先在业务代码中使用。

js 复制代码
import { serialize, deserialize } from "bun:jsc";

const buf = serialize({ foo: "bar" });
const obj = deserialize(buf);
console.log(obj); // => { foo: "bar" }

这里要注意,这个序列化方法,并不是标准方法,而是由JSC(Bun的执行引擎)提供的,可能会有一些可移植性和外部兼容性的问题。

而且,技术文档中也提到,这个方法,也被应用到structuredClone和postMessage等场景当中,是一个比较基础和常用的特性。

estimateShallowMemoryUsageOf

这也是一个JSC模块中的基础功能,可以帮助开发者评估程序运行过程中,各种对象单元对于浅内存占用的情况。当然这个值不包括其引用的属性和其他对象(可能要使用Bun.generateHeapSnapshot)。这些信息,对于开发者有效的对应用程序进行优化,显然有很大的帮助。

js 复制代码
import { estimateShallowMemoryUsageOf } from "bun:jsc";

const obj = { foo: "bar" };
const usage = estimateShallowMemoryUsageOf(obj);
console.log(usage); // => 16

const buffer = Buffer.alloc(1024 * 1024);
estimateShallowMemoryUsageOf(buffer);
// => 1048624

const req = new Request("https://bun.com");
estimateShallowMemoryUsageOf(req);
// => 167

const array = Array(1024).fill({ a: 1 });
// Arrays are usually not stored contiguously in memory, so this will not return a useful value (which isn't a bug).
estimateShallowMemoryUsageOf(array);
// => 16

REPL

这个是笔者自行增加的内容,并不是Utils的一部分。当前的Bun版本中,并没有内置的REPL,但如果你执行 bun repl,它可以自动下载并安装一个repl npm来执行,效果上和repl类似。

js 复制代码
[yanjh@localhost ssl]$ bun repl
Welcome to Bun v1.2.13
Type ".help" for more information.
[!] Please note that the REPL implementation is still experimental!
    Don't consider it to be representative of the stability or behavior of Bun overall.
> Bun.randomUUIDv7()
'01989773-752a-7000-90dc-9da9447cd7fb'

关于REPL本身,笔者已经有撰文讨论,这里不再赘述。这个特性,在学习Bun和JS语法,快速开发验证的场景,是非常实用的。

小结

在本文中,笔者例举了在bun中内置和相关的实用工具和特性。这些特性包括了sleep,randomUUIDv7,compress,inspect,readableStreamTo,serialize,nanoSeconds,memoryUsage、fileUrl、deepEquals等等常用和实用的功能,都可以为应用系统的开发提供很大的方便。

相关推荐
肩塔didi8 分钟前
用 Pixi 管理 Python 项目:打通Conda 和 PyPI 的边界
后端·python·github
teeeeeeemo16 分钟前
Ajax、Axios、Fetch核心区别
开发语言·前端·javascript·笔记·ajax
Juchecar17 分钟前
TypeScript对 null/undefined/==/===/!=/!== 等概念的支持,以及建议用法
javascript
柏成22 分钟前
基于 pnpm + monorepo 的 Qiankun微前端解决方案(内置模块联邦)
前端·javascript·面试
dylan_QAQ23 分钟前
【附录】相对于BeanFactory ,ApplicationContext 做了哪些企业化的增强?
后端·spring
唐诗36 分钟前
VMware Mac m系列安装 Windws 11,保姆级教程
前端·后端·github
Lx3521 小时前
Hadoop新手必知的10个高效操作技巧
hadoop·后端
写bug写bug1 小时前
搞懂Spring任务执行器和调度器模型
java·后端·spring
二闹1 小时前
TCP三次握手的智慧:为什么不是两次或四次?
后端·tcp/ip
熊猫片沃子1 小时前
Maven在使用过程中的核心知识点总结
java·后端·maven