概述
本文是笔者的系列博文 《Bun技术评估》 中的第二十五篇。
在本文的内容中,笔者主要想要来探讨一下Bun中的一些零碎的,实用的功能,Utils(实用工具)。
同样,在Bun技术文档中,有一个相关的章节:
笔者初步的研究了一下,发现了很多有趣的东西。很多内容其实也不复杂,但非常实用,可以解决一些以前在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的规范,将一些字符进行转义。例如:
字符 | 转义 |
---|---|
" | " |
& | & |
' | ' |
< | < |
/> | > |
在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等等常用和实用的功能,都可以为应用系统的开发提供很大的方便。