js-rpc2 2.3.1 → 2.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/doc/worker_threads.md +187 -0
- package/package.json +1 -1
- package/src/lib.js +60 -14
@@ -0,0 +1,187 @@
|
|
1
|
+
# 在 Node.js Worker Threads 中使用 js-rpc
|
2
|
+
|
3
|
+
Node.js Worker Threads 是一种在 Node.js 中实现并行处理的机制,js-rpc 提供了在主线程和 Worker 线程之间进行 RPC 调用的能力。
|
4
|
+
|
5
|
+
## 概述
|
6
|
+
|
7
|
+
js-rpc 在 Node.js Worker Threads 中的使用主要包括两个部分:
|
8
|
+
1. 在 Worker 线程中创建 RPC 服务器:使用 [createRpcServerNodeJSWorker](../src/lib.js#L993)
|
9
|
+
2. 在主线程中创建 RPC 客户端:使用 [createRpcClientNodeJSWorker](../src/lib.js#L1013)
|
10
|
+
|
11
|
+
## 完整示例
|
12
|
+
|
13
|
+
### 1. Worker 线程代码 (worker.js)
|
14
|
+
|
15
|
+
```js
|
16
|
+
// 导入必要的模块
|
17
|
+
import { parentPort } from 'worker_threads'
|
18
|
+
import { createRpcServerNodeJSWorker } from 'js-rpc2/src/lib.js'
|
19
|
+
|
20
|
+
// 定义可被远程调用的函数
|
21
|
+
async function readClipboard() {
|
22
|
+
// 实现读取剪贴板的逻辑
|
23
|
+
// 这里只是示例,实际实现取决于你的需求
|
24
|
+
return 'clipboard content'
|
25
|
+
}
|
26
|
+
|
27
|
+
async function writeClipboard(data) {
|
28
|
+
// 实现写入剪贴板的逻辑
|
29
|
+
console.log('Writing to clipboard:', data)
|
30
|
+
return true
|
31
|
+
}
|
32
|
+
|
33
|
+
async function writeClipboardText(text) {
|
34
|
+
// 实现写入文本到剪贴板的逻辑
|
35
|
+
console.log('Writing text to clipboard:', text)
|
36
|
+
return true
|
37
|
+
}
|
38
|
+
|
39
|
+
async function readClipboardText() {
|
40
|
+
// 实现读取剪贴板文本的逻辑
|
41
|
+
return 'clipboard text'
|
42
|
+
}
|
43
|
+
|
44
|
+
async function readClipboardHtml() {
|
45
|
+
// 实现读取剪贴板 HTML 内容的逻辑
|
46
|
+
return '<p>clipboard html</p>'
|
47
|
+
}
|
48
|
+
|
49
|
+
async function readClipboardImage() {
|
50
|
+
// 实现读取剪贴板图片的逻辑
|
51
|
+
return new Uint8Array([1, 2, 3, 4]) // 示例二进制数据
|
52
|
+
}
|
53
|
+
|
54
|
+
// 将所有函数导出为一个 API 对象
|
55
|
+
export const ExtensionApi = {
|
56
|
+
readClipboard,
|
57
|
+
writeClipboard,
|
58
|
+
writeClipboardText,
|
59
|
+
readClipboardText,
|
60
|
+
readClipboardHtml,
|
61
|
+
readClipboardImage,
|
62
|
+
}
|
63
|
+
|
64
|
+
// 创建 RPC 服务器
|
65
|
+
createRpcServerNodeJSWorker({
|
66
|
+
parentPort,
|
67
|
+
extension: ExtensionApi,
|
68
|
+
logger: (msg) => console.log('[Worker]', msg) // 可选的日志记录器
|
69
|
+
})
|
70
|
+
```
|
71
|
+
|
72
|
+
### 2. 主线程代码 (main.js)
|
73
|
+
|
74
|
+
```js
|
75
|
+
// 导入必要的模块
|
76
|
+
import { createRpcClientNodeJSWorker } from 'js-rpc2/src/lib.js'
|
77
|
+
import { Worker } from 'worker_threads'
|
78
|
+
|
79
|
+
// 创建 Worker 实例
|
80
|
+
export const worker = new Worker(new URL('./worker.js', import.meta.url))
|
81
|
+
|
82
|
+
// 创建 RPC 客户端
|
83
|
+
/** @type{ExtensionApi} */
|
84
|
+
const rpc = createRpcClientNodeJSWorker({ worker: worker })
|
85
|
+
|
86
|
+
// 取消 Worker 的引用,允许程序在没有其他任务时退出
|
87
|
+
worker.unref()
|
88
|
+
|
89
|
+
// 包装远程调用函数
|
90
|
+
export async function readClipboard() {
|
91
|
+
return await rpc.readClipboard()
|
92
|
+
}
|
93
|
+
|
94
|
+
/**
|
95
|
+
* 写入剪贴板内容
|
96
|
+
* @param {'text'|'richtext'|'image'} type - 数据类型
|
97
|
+
* @param {string|Buffer} data - 要写入的数据
|
98
|
+
*/
|
99
|
+
export async function writeClipboard(type, data) {
|
100
|
+
return await rpc.writeClipboard(type, data)
|
101
|
+
}
|
102
|
+
|
103
|
+
/**
|
104
|
+
* 写入文本到剪贴板
|
105
|
+
* @param {string} text - 要写入的文本
|
106
|
+
*/
|
107
|
+
export async function writeClipboardText(text) {
|
108
|
+
return rpc.writeClipboardText(text)
|
109
|
+
}
|
110
|
+
|
111
|
+
export async function readClipboardText() {
|
112
|
+
return rpc.readClipboardText()
|
113
|
+
}
|
114
|
+
|
115
|
+
export async function readClipboardHtml() {
|
116
|
+
return rpc.readClipboardHtml()
|
117
|
+
}
|
118
|
+
|
119
|
+
export async function readClipboardImage() {
|
120
|
+
return rpc.readClipboardImage()
|
121
|
+
}
|
122
|
+
|
123
|
+
// 使用示例
|
124
|
+
async function example() {
|
125
|
+
// 调用远程函数
|
126
|
+
const text = await readClipboardText()
|
127
|
+
console.log('Clipboard text:', text)
|
128
|
+
|
129
|
+
await writeClipboardText('Hello from main thread!')
|
130
|
+
|
131
|
+
const image = await readClipboardImage()
|
132
|
+
console.log('Clipboard image size:', image.length)
|
133
|
+
}
|
134
|
+
```
|
135
|
+
|
136
|
+
## API 说明
|
137
|
+
|
138
|
+
### createRpcServerNodeJSWorker
|
139
|
+
|
140
|
+
在 Worker 线程中创建 RPC 服务器。
|
141
|
+
|
142
|
+
```js
|
143
|
+
/**
|
144
|
+
* @param {{
|
145
|
+
* parentPort: NodeJSMessagePort;
|
146
|
+
* extension: Object;
|
147
|
+
* logger?:(msg:string)=>void;
|
148
|
+
* }} param
|
149
|
+
*/
|
150
|
+
export function createRpcServerNodeJSWorker(param)
|
151
|
+
```
|
152
|
+
|
153
|
+
参数说明:
|
154
|
+
- `parentPort`: Worker 线程的 parentPort 对象,用于与主线程通信
|
155
|
+
- `extension`: 包含可被远程调用函数的对象
|
156
|
+
- `logger`: 可选的日志记录函数
|
157
|
+
|
158
|
+
### createRpcClientNodeJSWorker
|
159
|
+
|
160
|
+
在主线程中创建 RPC 客户端。
|
161
|
+
|
162
|
+
```js
|
163
|
+
/**
|
164
|
+
* @param {{
|
165
|
+
* worker:NodeJSWorker;
|
166
|
+
* }} param
|
167
|
+
*/
|
168
|
+
export function createRpcClientNodeJSWorker(param)
|
169
|
+
```
|
170
|
+
|
171
|
+
参数说明:
|
172
|
+
- `worker`: Worker 实例
|
173
|
+
|
174
|
+
## 特性
|
175
|
+
|
176
|
+
1. **透明的远程调用**:在主线程中调用 Worker 中的函数就像调用本地函数一样简单
|
177
|
+
2. **支持异步函数**:所有远程函数都可以是异步的
|
178
|
+
3. **参数和返回值序列化**:自动处理参数和返回值的序列化/反序列化
|
179
|
+
4. **类型安全**:通过 JSDoc 注解提供完整的类型支持
|
180
|
+
5. **错误处理**:远程函数抛出的错误会正确传播到调用方
|
181
|
+
|
182
|
+
## 注意事项
|
183
|
+
|
184
|
+
1. 确保 Worker 线程中的函数是可序列化的
|
185
|
+
2. 大数据传输可能影响性能,考虑使用流式传输
|
186
|
+
3. 避免在远程函数中使用闭包引用 Worker 外部的变量
|
187
|
+
4. Worker 线程在没有任务时会自动退出,如需保持运行请适当管理引用
|
package/package.json
CHANGED
package/src/lib.js
CHANGED
@@ -2,6 +2,7 @@ import { Packr } from 'msgpackr'
|
|
2
2
|
|
3
3
|
/**
|
4
4
|
* @import { CALLBACK_ITEM, RPC_DATA, RPC_DATA_ARG_ITEM } from "./types.js"
|
5
|
+
* @import { MessagePort as NodeJSMessagePort, Worker as NodeJSWorker } from 'node:worker_threads'
|
5
6
|
*/
|
6
7
|
|
7
8
|
const JS_RPC_WITH_CRYPTO = true
|
@@ -774,10 +775,10 @@ export function _testCreateRpcClientHttp(param) {
|
|
774
775
|
method: 'POST',
|
775
776
|
signal: param.signal,
|
776
777
|
// @ts-ignore
|
777
|
-
duplex:'half',
|
778
|
+
duplex: 'half',
|
778
779
|
body: new ReadableStream({
|
779
|
-
async pull(controller){
|
780
|
-
controller.enqueue(chunk.slice(0,10))
|
780
|
+
async pull(controller) {
|
781
|
+
controller.enqueue(chunk.slice(0, 10))
|
781
782
|
await sleep(1000)
|
782
783
|
controller.enqueue(chunk.slice(10))
|
783
784
|
controller.close()
|
@@ -873,25 +874,28 @@ export function createRpcClientMessagePort(param) {
|
|
873
874
|
|
874
875
|
/**
|
875
876
|
* @param {string} text
|
876
|
-
* @returns
|
877
877
|
*/
|
878
878
|
export function base64decode(text) {
|
879
|
-
const
|
880
|
-
const
|
881
|
-
|
882
|
-
|
879
|
+
const binaryString = atob(text)
|
880
|
+
const len = binaryString.length
|
881
|
+
const bytes = new Uint8Array(len)
|
882
|
+
for (let i = 0; i < len; i++) {
|
883
|
+
bytes[i] = binaryString.charCodeAt(i)
|
884
|
+
}
|
885
|
+
return bytes
|
883
886
|
}
|
884
887
|
|
885
888
|
/**
|
886
889
|
* @param {Uint8Array<ArrayBuffer>} buffer
|
887
|
-
* @returns
|
888
890
|
*/
|
889
|
-
export function base64encode(buffer
|
890
|
-
|
891
|
-
|
892
|
-
|
891
|
+
export function base64encode(buffer) {
|
892
|
+
const CHUNK_SZ = 0x8000
|
893
|
+
const chars = []
|
894
|
+
for (let i = 0; i < buffer.length; i += CHUNK_SZ) {
|
895
|
+
const chunk = buffer.subarray(i, i + CHUNK_SZ)
|
896
|
+
chars.push(String.fromCharCode.apply(null, chunk))
|
893
897
|
}
|
894
|
-
return
|
898
|
+
return btoa(chars.join(''))
|
895
899
|
}
|
896
900
|
|
897
901
|
/**
|
@@ -986,3 +990,45 @@ export function createRpcClientChromeExtensions(param) {
|
|
986
990
|
})()
|
987
991
|
return createRPCProxy(helper.apiInvoke)
|
988
992
|
}
|
993
|
+
|
994
|
+
/**
|
995
|
+
* @param {{
|
996
|
+
* parentPort: NodeJSMessagePort;
|
997
|
+
* extension: Object;
|
998
|
+
* logger?:(msg:string)=>void;
|
999
|
+
* }} param
|
1000
|
+
*/
|
1001
|
+
export function createRpcServerNodeJSWorker(param) {
|
1002
|
+
const port = param.parentPort
|
1003
|
+
let helper = createRpcServerHelper({
|
1004
|
+
rpcKey: '', extension: param.extension, async: true, logger: param.logger
|
1005
|
+
})
|
1006
|
+
let writer = helper.writable.getWriter()
|
1007
|
+
port.on('message', async (data) => {
|
1008
|
+
await writer.write(data)
|
1009
|
+
})
|
1010
|
+
helper.readable.pipeTo(new WritableStream({
|
1011
|
+
async write(chunk) {
|
1012
|
+
port.postMessage(chunk)
|
1013
|
+
}
|
1014
|
+
}))
|
1015
|
+
}
|
1016
|
+
|
1017
|
+
/**
|
1018
|
+
* @param {{
|
1019
|
+
* worker:NodeJSWorker;
|
1020
|
+
* }} param
|
1021
|
+
*/
|
1022
|
+
export function createRpcClientNodeJSWorker(param) {
|
1023
|
+
let helper = createRpcClientHelper({ rpcKey: '' })
|
1024
|
+
let writer = helper.writable.getWriter()
|
1025
|
+
helper.readable.pipeTo(new WritableStream({
|
1026
|
+
async write(chunk) {
|
1027
|
+
param.worker.postMessage(chunk)
|
1028
|
+
}
|
1029
|
+
}))
|
1030
|
+
param.worker.on('message', async (data) => {
|
1031
|
+
await writer.write(data)
|
1032
|
+
})
|
1033
|
+
return createRPCProxy(helper.apiInvoke)
|
1034
|
+
}
|