release v0.1.0
This commit is contained in:
371
README.md
Normal file
371
README.md
Normal file
@@ -0,0 +1,371 @@
|
||||
# ff-worker-pipe
|
||||
|
||||
在独立 Web Worker 中执行代码,通过链式调用透明访问主线程注册的函数(HTTP 客户端、数据处理、路由等),并支持将 Worker 侧的回调函数跨线程传回主线程执行。
|
||||
|
||||
## 安装
|
||||
|
||||
```bash
|
||||
npm install ff-worker-pipe
|
||||
```
|
||||
|
||||
## 工作机制
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────────┐
|
||||
│ 主线程 (Main Thread) │
|
||||
│ │
|
||||
│ WorkerPipe.fns = { transform, http, route, … } │
|
||||
│ ↑ 注册主线程函数 │
|
||||
│ pipe.exec(`return Util.transform([1,2,3]).map(x => x*2)`) │
|
||||
│ ↓ 发送 eval 消息 │
|
||||
└──────────────────────────┬──────────────────────────────────────────┘
|
||||
│ postMessage
|
||||
┌──────────────────▼──────────────────────┐
|
||||
│ Web Worker │
|
||||
│ │
|
||||
│ eval → 执行代码字符串 │
|
||||
│ Util.transform([1,2,3]) │
|
||||
│ → util 消息 → 主线程执行 │
|
||||
│ ← 返回 { __chain_id__ } │
|
||||
│ .map(x => x*2) │
|
||||
│ → util 消息 + fn_ref → 主线程 │
|
||||
│ → invoke_fn → Worker 执行 x=>x*2 │
|
||||
│ ← invoke_fn_result → 组装结果 │
|
||||
│ return [2, 4, 6] │
|
||||
│ → eval 结果 → 主线程 resolve │
|
||||
└──────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### 四类消息协议
|
||||
|
||||
| 消息类型 | 方向 | 触发时机 |
|
||||
|---------|------|---------|
|
||||
| `eval` | 主线程 → Worker | `pipe.exec(code)` 调用时 |
|
||||
| `util` | Worker ↔ 主线程 | Worker 侧调用 `Util.xxx()` 时(双向:请求 + 响应) |
|
||||
| `invoke_fn` | 主线程 → Worker | 主线程需要执行 Worker 侧传来的回调函数时 |
|
||||
| `clear` | 主线程 → Worker | `pipe.clear(session)` 清除工作区时 |
|
||||
|
||||
### 基本调用流程
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant MT as 主线程
|
||||
participant W as Worker
|
||||
|
||||
MT->>W: ① eval { id, method: "return Util('async').double(5)" }
|
||||
Note over W: eval 创建 async 函数并执行
|
||||
Note over W: Util('async').double(5)<br/>→ send('async/double', [5])
|
||||
W->>MT: ② util { id:0, task_id, method:'async/double', args:[5] }
|
||||
Note over MT: handleUtil<br/>→ fns.async.double(5)<br/>→ Promise‹10›
|
||||
MT->>W: ③ util { id:0, data:10, error:null }
|
||||
Note over W: mQueue 回调,Promise 解析为 10<br/>async fn return 10
|
||||
W->>MT: ④ eval { id, data:10, error:null }
|
||||
Note over MT: pipe.exec() Promise.resolve(10)
|
||||
```
|
||||
|
||||
### WithCallback 回调流程
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant MT as 主线程
|
||||
participant W as Worker
|
||||
|
||||
MT->>W: ① eval { method: "return Util.transform([1,2,3]).map(x=>x*2)" }
|
||||
|
||||
Note over W: Util.transform([1,2,3])<br/>→ send('transform', [[1,2,3]])
|
||||
W->>MT: ② util { method:'transform', args:[[1,2,3]] }
|
||||
Note over MT: WithCallback(transform)([1,2,3])<br/>→ { map:wrapFn, filter:wrapFn, … }<br/>→ resolveResult → chainStore[cid]
|
||||
MT->>W: ③ util { data:{ __chain_id__:cid } }
|
||||
|
||||
Note over W: .map(x=>x*2)<br/>→ storeArg(fn) → { __fn_id__:'fn_0' }<br/>→ send('map', [fn_ref], cid)
|
||||
W->>MT: ④ util { method:'map', args:[{__fn_id__:'fn_0'}], chain_id:cid }
|
||||
Note over MT: wrapArg → Proxy fn<br/>wrapFn(map)(proxy) → data.map(proxy)<br/>pending = [{fn:proxy,params:[1]},…]<br/>Promise.all → invokeWorkerFn('fn_0',[1])
|
||||
MT->>W: ⑤ invoke_fn { fn_id:'fn_0', cb_id:0, args:[1] }
|
||||
Note over W: fnStore.get('fn_0') = x=>x*2<br/>fn(1) = 2
|
||||
W->>MT: ⑥ invoke_fn_result { cb_id:0, result:2 }
|
||||
Note over MT: 重复 ⑤⑥ for 2→4, 3→6<br/>Promise.all([2,4,6])<br/>占位符替换 → [2,4,6]
|
||||
MT->>W: ⑦ util { data:[2,4,6] }
|
||||
|
||||
Note over W: .map() 解析为 [2,4,6]<br/>async fn return [2,4,6]
|
||||
W->>MT: ⑧ eval { data:[2,4,6] }
|
||||
Note over MT: pipe.exec() Promise.resolve([2,4,6])
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 快速上手
|
||||
|
||||
### 1. 主线程
|
||||
|
||||
```js
|
||||
import { WorkerPipe, WithCallback } from 'ff-worker-pipe/main'
|
||||
|
||||
// 注册可供 Worker 侧调用的函数
|
||||
WorkerPipe.fns = {
|
||||
// 普通函数
|
||||
getUser: (id) => fetchUser(id),
|
||||
|
||||
// 命名空间
|
||||
format: {
|
||||
date: (ts) => new Date(ts).toLocaleDateString(),
|
||||
},
|
||||
|
||||
// 多层级链式调用(无回调参数,不需要 WithCallback)
|
||||
db: {
|
||||
query: (table) => ({
|
||||
where: (field, value) => ({
|
||||
select: (...fields) => fetchFromDB(table, { [field]: value }, fields),
|
||||
}),
|
||||
}),
|
||||
},
|
||||
|
||||
// 含回调参数的函数用 WithCallback 包裹
|
||||
transform: WithCallback((data) => ({
|
||||
map: (fn) => data.map(fn),
|
||||
filter: (fn) => data.filter(fn),
|
||||
sort: () => ({ // sort 不接受比较器回调(需同步执行)
|
||||
map: (fn) => [...data].sort((a, b) => a - b).map(fn),
|
||||
filter: (fn) => [...data].sort((a, b) => a - b).filter(fn),
|
||||
}),
|
||||
})),
|
||||
}
|
||||
|
||||
// 初始化(单例,首次调用时传 URL)
|
||||
const pipe = new WorkerPipe(new URL('./worker.js', import.meta.url), { type: 'module' })
|
||||
|
||||
// 执行 Worker 代码
|
||||
const result = await pipe.exec(`return Util.transform([3, 1, 2]).sort().map(x => x * 10)`)
|
||||
// → [10, 20, 30]
|
||||
```
|
||||
|
||||
### 2. Worker 入口
|
||||
|
||||
`ff-worker-pipe/worker` 打包后即为完整的 Worker 入口文件,直接指向即可:
|
||||
|
||||
```js
|
||||
// worker.js(你自己的 worker 入口)
|
||||
import 'ff-worker-pipe/worker'
|
||||
// 文件加载时自动初始化,无需任何额外代码
|
||||
```
|
||||
|
||||
或在主线程直接指向包内的 worker 文件(开发环境):
|
||||
|
||||
```js
|
||||
const pipe = new WorkerPipe(
|
||||
new URL('../node_modules/ff-worker-pipe/dist/worker.js', import.meta.url)
|
||||
)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Worker 侧调用语法
|
||||
|
||||
在 `pipe.exec()` 的代码字符串中,通过 `Util` 访问主线程注册的函数:
|
||||
|
||||
```js
|
||||
// 顶层函数
|
||||
const user = await Util.getUser(1)
|
||||
|
||||
// 命名空间
|
||||
const str = await Util('format').date(Date.now())
|
||||
|
||||
// 多层链式(无回调)
|
||||
const rows = await Util('db').query('users').where('age', 18).select('name', 'email')
|
||||
|
||||
// WithCallback 链式(含回调)
|
||||
const mapped = await Util.transform([1, 2, 3]).map(x => x * 2)
|
||||
const filtered = await Util.transform([1, 2, 3]).filter(x => x > 1)
|
||||
const sorted = await Util.transform([3, 1, 2]).sort().map(x => x * 10)
|
||||
|
||||
// 直接 await(无 .then())
|
||||
const { id, name } = await Util('http').get('/api/user')
|
||||
|
||||
// .then() 提取字段
|
||||
const name = await Util('http').get('/api/user').then(d => d.name)
|
||||
|
||||
// 顺序 await + 计算
|
||||
const { value: a } = await Util('http').get('/api/a')
|
||||
const b = await Util('http').get('/api/b').then(d => d.value)
|
||||
return a + b
|
||||
|
||||
// Promise.all 并发
|
||||
const [{ n: a }, { n: b }] = await Promise.all([
|
||||
Util('http').get('/api/a'),
|
||||
Util('http').get('/api/b'),
|
||||
])
|
||||
return a * b
|
||||
|
||||
// 错误处理
|
||||
const result = await Util('http').get('/api/data')
|
||||
.then(d => d)
|
||||
.catch(e => null)
|
||||
```
|
||||
|
||||
### session 工作区
|
||||
|
||||
相同 `session` 的多次 `exec()` 共享同一个 `this`(Worker 侧工作区),可在调用间保留状态:
|
||||
|
||||
```js
|
||||
await pipe.exec(`this.count = (this.count || 0) + 1`, {}, {}, 'my-session')
|
||||
await pipe.exec(`this.count = (this.count || 0) + 1`, {}, {}, 'my-session')
|
||||
const n = await pipe.exec(`return this.count`, {}, {}, 'my-session')
|
||||
// n === 2
|
||||
|
||||
pipe.clear('my-session') // 清除工作区
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## WithCallback 详解
|
||||
|
||||
对含**回调类型参数**的函数使用 `WithCallback` 包裹,不需要修改函数本身的逻辑。
|
||||
|
||||
```js
|
||||
// 原始函数,保持同步写法
|
||||
const process = (data) => ({
|
||||
map: (fn) => data.map(fn),
|
||||
filter: (fn) => data.filter(fn),
|
||||
reduce: (fn, init) => data.reduce(fn, init),
|
||||
})
|
||||
|
||||
// 注册时包裹
|
||||
WorkerPipe.fns = {
|
||||
process: WithCallback(process),
|
||||
|
||||
// 对象形式,批量包裹
|
||||
...WithCallback({ process, transform }),
|
||||
}
|
||||
```
|
||||
|
||||
Worker 侧直接传函数,无需额外处理:
|
||||
|
||||
```js
|
||||
const doubled = await Util.process([1, 2, 3]).map(x => x * 2) // [2, 4, 6]
|
||||
const positive = await Util.process([1, -2, 3]).filter(x => x > 0) // [1, 3]
|
||||
```
|
||||
|
||||
**占位符机制**(内部实现):`WithCallback` 在主线程侧将 Worker 回调替换为占位符字符串 `\x00idx\x00`,以占位符调用原始函数得到含占位符的结果,然后并发执行所有 Worker 回调(通过 `invoke_fn` 往返消息),最后将占位符替换为真实结果。
|
||||
|
||||
---
|
||||
|
||||
## async fn 支持
|
||||
|
||||
`WorkerPipe.fns` 中的函数可以是 `async` 函数或返回 `Promise`,Worker 侧无需特殊处理:
|
||||
|
||||
```js
|
||||
WorkerPipe.fns = {
|
||||
async: {
|
||||
// async 函数
|
||||
double: async (x) => x * 2,
|
||||
|
||||
// 显式返回 Promise
|
||||
delayed: (x) => Promise.resolve(x * 3),
|
||||
|
||||
// async fn → 返回含方法的对象(同步 + 异步方法均支持)
|
||||
build: async (data) => ({
|
||||
sum: () => data.reduce((a, b) => a + b, 0), // 同步方法
|
||||
max: async () => Math.max(...data), // async 方法
|
||||
doubled: () => data.map(x => x * 2), // 同步方法
|
||||
}),
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
Worker 侧:
|
||||
|
||||
```js
|
||||
// 基础 async
|
||||
const n = await Util('async').double(5) // 10
|
||||
|
||||
// 链式:async fn → 含方法的对象
|
||||
const sum = await Util('async').build([1, 2, 3]).sum() // 6
|
||||
const max = await Util('async').build([1, 2, 3]).max() // 3
|
||||
|
||||
// Worker 侧 Promise.all
|
||||
const [a, b] = await Promise.all([
|
||||
Util('async').double(3),
|
||||
Util('async').double(7),
|
||||
])
|
||||
return a + b // 20
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 自定义可链式类型
|
||||
|
||||
对于不可序列化但需要链式调用的对象(如 HTTP 客户端响应),通过 `WorkerPipe.chainableTypes` 注册:
|
||||
|
||||
```js
|
||||
import { HttpResponse } from 'ff-request'
|
||||
|
||||
WorkerPipe.chainableTypes = [HttpResponse]
|
||||
WorkerPipe.fns = { http: new HttpRequest() }
|
||||
```
|
||||
|
||||
主线程检测到 `instanceof HttpResponse` 时,将对象存入 `chainStore` 而非序列化,Worker 侧可继续调用其方法(`.then()`、`.catch()`、`.msg()` 等)。
|
||||
|
||||
---
|
||||
|
||||
## API
|
||||
|
||||
### 主线程 (`ff-worker-pipe/main`)
|
||||
|
||||
#### `WorkerPipe`
|
||||
|
||||
| 成员 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| `fns` | `static Object` | 注册可供 Worker 调用的函数树 |
|
||||
| `chainableTypes` | `static Array` | 需要存入 chainStore 的类型列表 |
|
||||
| `new WorkerPipe(url, options?)` | constructor | 创建实例(单例),`options` 同 `new Worker()` |
|
||||
| `WorkerPipe.getInstance(url, options?)` | static | 获取或创建单例 |
|
||||
| `pipe.exec(source, args?, fns?, session?)` | method | 在 Worker 中执行代码字符串,返回 `Promise` |
|
||||
| `pipe.clear(session)` | method | 清除指定 session 的 Worker 工作区 |
|
||||
|
||||
#### `WithCallback(fnOrObj)`
|
||||
|
||||
| 传入形式 | 说明 |
|
||||
|---------|------|
|
||||
| `WithCallback(fn)` | 包裹单个函数 |
|
||||
| `WithCallback({ fn, … })` | 批量包裹对象中的函数 |
|
||||
|
||||
### Worker 侧 (`ff-worker-pipe/worker`)
|
||||
|
||||
打包后直接作为 Worker 入口文件使用,自动初始化。
|
||||
|
||||
如需手动控制初始化:
|
||||
|
||||
```js
|
||||
import { createWorkerSide } from 'ff-worker-pipe/worker'
|
||||
|
||||
const { Util, onMessage, clearSession } = createWorkerSide(self)
|
||||
self.addEventListener('message', onMessage)
|
||||
self.addEventListener('message', ({ data }) => {
|
||||
if (data.category === 'clear') clearSession(data.session)
|
||||
})
|
||||
self.addEventListener('message', ({ data: { id, method, args, category, session } }) => {
|
||||
if (category !== 'eval') return
|
||||
const fn = eval(`(async function({${Object.keys(args).join(',')}}, Util){ ${method} })`)
|
||||
Promise.resolve(fn.call({}, args, Util(id, session)))
|
||||
.then(data => self.postMessage({ id, session, category, data, error: null }))
|
||||
.catch(e => self.postMessage({ id, session, category, data: null, error: String(e) }))
|
||||
})
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 构建
|
||||
|
||||
```bash
|
||||
yarn build # 输出 dist/main.js(库模块)和 dist/worker.js(Worker 入口)
|
||||
```
|
||||
|
||||
`package.json` exports:
|
||||
|
||||
```json
|
||||
{
|
||||
"exports": {
|
||||
"./main": { "development": "./src/main.js", "default": "./dist/main.js" },
|
||||
"./worker": { "development": "./src/worker.js", "default": "./dist/worker.js" }
|
||||
}
|
||||
}
|
||||
```
|
||||
140
main.js
Normal file
140
main.js
Normal file
@@ -0,0 +1,140 @@
|
||||
var I = Object.defineProperty;
|
||||
var E = (n) => {
|
||||
throw TypeError(n);
|
||||
};
|
||||
var J = (n, e, s) => e in n ? I(n, e, { enumerable: !0, configurable: !0, writable: !0, value: s }) : n[e] = s;
|
||||
var j = (n, e, s) => J(n, typeof e != "symbol" ? e + "" : e, s), D = (n, e, s) => e.has(n) || E("Cannot " + s);
|
||||
var c = (n, e, s) => (D(n, e, "read from private field"), s ? s.call(n) : e.get(n)), Q = (n, e, s) => e.has(n) ? E("Cannot add the same private member more than once") : e instanceof WeakSet ? e.add(n) : e.set(n, s), S = (n, e, s, i) => (D(n, e, "write to private field"), i ? i.call(n, s) : e.set(n, s), s);
|
||||
var P = (n, e, s, i) => ({
|
||||
set _(f) {
|
||||
S(n, e, f, s);
|
||||
},
|
||||
get _() {
|
||||
return c(n, e, i);
|
||||
}
|
||||
});
|
||||
const T = (n, e) => e.reduce((s, i) => s == null ? void 0 : s[i], n), U = (n) => n !== null && typeof n == "object" && Object.getPrototypeOf(n) === Object.prototype, N = (n) => {
|
||||
const e = (i) => U(i) ? Object.fromEntries(Object.entries(i).map(
|
||||
([f, u]) => typeof u == "function" ? [f, s(u)] : [f, u]
|
||||
)) : i, s = (i) => (...f) => {
|
||||
const u = [], x = f.map((h) => typeof h != "function" ? h : (...d) => {
|
||||
const t = u.length;
|
||||
return u.push({ fn: h, params: d }), `\0${t}\0`;
|
||||
}), a = e(i(...x));
|
||||
return u.length === 0 ? a : Promise.all(u.map(({ fn: h, params: d }) => h(...d))).then((h) => typeof a == "string" ? a.replace(/\x00(\d+)\x00/g, (d, t) => h[+t]) : Array.isArray(a) ? a.some((t) => typeof t == "string" && /^\x00\d+\x00$/.test(t)) ? a.map((t) => typeof t == "string" && /^\x00\d+\x00$/.test(t) ? h[+t.slice(1, -1)] : t) : u.filter((t, o) => h[o]).map(({ params: t }) => t[0]) : a);
|
||||
};
|
||||
return U(n) ? Object.fromEntries(Object.entries(n).map(
|
||||
([i, f]) => typeof f == "function" ? [i, N(f)] : [i, f]
|
||||
)) : (...i) => e(n(...i));
|
||||
};
|
||||
var A, M, w, y;
|
||||
const r = class r {
|
||||
/**
|
||||
* @param {string|URL} workerUrl Worker 脚本地址
|
||||
* @param {WorkerOptions} [options] 传给 new Worker() 的选项,如 { type: 'module' }
|
||||
*/
|
||||
constructor(e, s = {}) {
|
||||
/** 底层 Worker 实例 */
|
||||
Q(this, y);
|
||||
/**
|
||||
* 在 Worker 中执行一段代码字符串,返回其 return 值。
|
||||
*
|
||||
* @param {string} source 代码字符串,支持 async/await,可使用 Util 访问主线程函数
|
||||
* @param {Object} [args] 注入代码的变量(以对象属性形式解构到函数参数中)
|
||||
* @param {Object} [fns] 本次任务私有的函数注册,优先级低于 WorkerPipe.fns
|
||||
* @param {string} [session] session 标识,相同 session 共享 Worker 侧工作区(this)
|
||||
* @returns {Promise<any>}
|
||||
*/
|
||||
j(this, "exec", (e, s = {}, i = {}, f = "") => new Promise((u, x) => {
|
||||
if (!/^(?!\s*(\/\/|\/\*|\*)).*?\S+/m.test(e)) return u();
|
||||
const a = P(r, A)._++;
|
||||
c(r, M).set(a, i), r.mQueue.set(a, [u, x]), c(this, y).postMessage({ id: a, session: f, category: "eval", method: e, args: s });
|
||||
}));
|
||||
/** 清除指定 session 的 Worker 侧工作区 */
|
||||
j(this, "clear", (e) => c(this, y).postMessage({ session: e, category: "clear" }));
|
||||
if (c(r, w)) return c(r, w);
|
||||
S(this, y, new Worker(e, s));
|
||||
const i = /* @__PURE__ */ new Map(), f = /* @__PURE__ */ new Map();
|
||||
let u = 0;
|
||||
const x = (t) => {
|
||||
const o = crypto.randomUUID();
|
||||
return i.set(o, t), setTimeout(() => i.delete(o), 3e4), o;
|
||||
};
|
||||
r.invokeWorkerFn = (t, o) => new Promise((p, b) => {
|
||||
const g = u++;
|
||||
f.set(g, [p, b]), c(this, y).postMessage({ category: "invoke_fn", fn_id: t, cb_id: g, args: o });
|
||||
});
|
||||
const a = (t) => {
|
||||
if (!(t !== null && typeof t == "object" && Object.getPrototypeOf(t) === Object.prototype && Object.values(t).some((g) => typeof g == "function"))) return t;
|
||||
const p = x(t);
|
||||
return { ...Object.fromEntries(Object.entries(t).filter(([, g]) => typeof g != "function")), __chain_id__: p };
|
||||
}, h = (t) => t != null && t.__fn_id__ ? new Proxy(function() {
|
||||
}, {
|
||||
apply: (o, p, b) => r.invokeWorkerFn(t.__fn_id__, b)
|
||||
}) : t, d = ({ id: t, task_id: o, method: p, args: b, session: g, chain_id: $ }) => {
|
||||
try {
|
||||
let m;
|
||||
if ($) {
|
||||
const l = i.get($);
|
||||
if (!l) throw `chain state '${$}' not found or expired`;
|
||||
if (m = l[p], typeof m != "function") throw `${p} is not a function in chain state`;
|
||||
m = m.bind(l);
|
||||
} else {
|
||||
const l = p.split("/");
|
||||
if (m = T(r.fns, l) || T(c(r, M).get(o), l), typeof m != "function") throw `${p} not found`;
|
||||
}
|
||||
const F = Reflect.apply(m, void 0, b.map(h));
|
||||
if (r.chainableTypes.some((l) => F instanceof l)) {
|
||||
const l = x(F);
|
||||
c(this, y).postMessage({ id: t, task_id: o, category: "util", method: p, args: b, session: g, data: { __chain_id__: l }, error: null });
|
||||
return;
|
||||
}
|
||||
Promise.resolve(F).then((l) => {
|
||||
c(this, y).postMessage({ id: t, task_id: o, category: "util", method: p, args: b, session: g, data: a(l), error: null });
|
||||
}).catch((l) => {
|
||||
let O;
|
||||
try {
|
||||
O = JSON.parse(JSON.stringify(l)), typeof O == "object" && !Array.isArray(O) && !Object.keys(O).length && (O = String(l));
|
||||
} catch {
|
||||
O = String(l);
|
||||
}
|
||||
c(this, y).postMessage({ id: t, task_id: o, category: "util", method: p, args: b, session: g, data: null, error: O });
|
||||
});
|
||||
} catch (m) {
|
||||
c(this, y).postMessage({ id: t, task_id: o, category: "util", method: p, args: b, session: g, data: null, error: String(m) });
|
||||
}
|
||||
};
|
||||
c(this, y).addEventListener("message", ({ data: t }) => {
|
||||
if (t.category === "util")
|
||||
d(t);
|
||||
else if (t.category === "invoke_fn_result") {
|
||||
const o = f.get(t.cb_id);
|
||||
if (!o) return;
|
||||
f.delete(t.cb_id), t.error !== null ? o[1](t.error) : o[0](t.result);
|
||||
} else t.category === "eval" && r.mQueue.has(t.id) && (t.error !== null ? r.mQueue.get(t.id)[1](t.error) : r.mQueue.get(t.id)[0](t.data), c(r, M).delete(t.id), r.mQueue.delete(t.id));
|
||||
}, !1), S(r, w, this);
|
||||
}
|
||||
/** 获取单例,首次调用时传入 Worker URL 和选项 */
|
||||
static getInstance(e, s = {}) {
|
||||
return c(r, w) || new r(e, s), c(r, w);
|
||||
}
|
||||
};
|
||||
A = new WeakMap(), M = new WeakMap(), w = new WeakMap(), y = new WeakMap(), /** 可供 Worker 侧调用的函数注册表(静态,全局共享) */
|
||||
j(r, "fns", {}), /** exec() 任务队列:task_id → [resolve, reject] */
|
||||
j(r, "mQueue", /* @__PURE__ */ new Map()), /**
|
||||
* 需要通过 chainStore 存储的自定义可链式类型(如 HttpResponse)。
|
||||
* instanceof 匹配时,主线程将对象存入 chainStore 而非序列化传输。
|
||||
*/
|
||||
j(r, "chainableTypes", []), /** 全局递增消息 id,用于 mQueue 匹配 */
|
||||
Q(r, A, 0), /** 每个 exec() 任务私有的 fns(第三参数),任务结束后清除 */
|
||||
Q(r, M, /* @__PURE__ */ new Map()), /** 单例引用 */
|
||||
Q(r, w, null), /**
|
||||
* 反向调用 Worker 回调的函数:由构造函数赋值,供 wrapArg 使用。
|
||||
* 签名:(fn_id: string, args: any[]) => Promise<any>
|
||||
*/
|
||||
j(r, "invokeWorkerFn", null);
|
||||
let C = r;
|
||||
export {
|
||||
N as WithCallback,
|
||||
C as WorkerPipe
|
||||
};
|
||||
9
package.json
Normal file
9
package.json
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"name": "ff-worker-pipe",
|
||||
"version": "0.1.0",
|
||||
"type": "module",
|
||||
"exports": {
|
||||
"./worker": "./worker.js",
|
||||
"./main": "./main.js"
|
||||
}
|
||||
}
|
||||
379
worker.js
Normal file
379
worker.js
Normal file
@@ -0,0 +1,379 @@
|
||||
var commonjsGlobal = typeof globalThis < "u" ? globalThis : typeof window < "u" ? window : typeof global < "u" ? global : typeof self < "u" ? self : {};
|
||||
function getDefaultExportFromCjs(_) {
|
||||
return _ && _.__esModule && Object.prototype.hasOwnProperty.call(_, "default") ? _.default : _;
|
||||
}
|
||||
var dayjs_min = { exports: {} };
|
||||
(function(_, S) {
|
||||
(function(L, O) {
|
||||
_.exports = O();
|
||||
})(commonjsGlobal, function() {
|
||||
var L = 1e3, O = 6e4, C = 36e5, w = "millisecond", y = "second", v = "minute", m = "hour", D = "day", b = "week", f = "month", d = "quarter", u = "year", h = "date", c = "Invalid Date", I = /^(\d{4})[-/]?(\d{1,2})?[-/]?(\d{0,2})[Tt\s]*(\d{1,2})?:?(\d{1,2})?:?(\d{1,2})?[.:]?(\d+)?$/, Z = /\[([^\]]+)]|YYYY|YY|M{1,4}|D{1,2}|d{1,4}|H{1,2}|h{1,2}|a|A|m{1,2}|s{1,2}|Z{1,2}|SSS/g, V = { name: "en", weekdays: "Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday".split("_"), months: "January_February_March_April_May_June_July_August_September_October_November_December".split("_"), ordinal: function(s) {
|
||||
var n = ["th", "st", "nd", "rd"], e = s % 100;
|
||||
return "[" + s + (n[(e - 20) % 10] || n[e] || n[0]) + "]";
|
||||
} }, G = function(s, n, e) {
|
||||
var r = String(s);
|
||||
return !r || r.length >= n ? s : "" + Array(n + 1 - r.length).join(e) + s;
|
||||
}, K = { s: G, z: function(s) {
|
||||
var n = -s.utcOffset(), e = Math.abs(n), r = Math.floor(e / 60), t = e % 60;
|
||||
return (n <= 0 ? "+" : "-") + G(r, 2, "0") + ":" + G(t, 2, "0");
|
||||
}, m: function s(n, e) {
|
||||
if (n.date() < e.date()) return -s(e, n);
|
||||
var r = 12 * (e.year() - n.year()) + (e.month() - n.month()), t = n.clone().add(r, f), i = e - t < 0, o = n.clone().add(r + (i ? -1 : 1), f);
|
||||
return +(-(r + (e - t) / (i ? t - o : o - t)) || 0);
|
||||
}, a: function(s) {
|
||||
return s < 0 ? Math.ceil(s) || 0 : Math.floor(s);
|
||||
}, p: function(s) {
|
||||
return { M: f, y: u, w: b, d: D, D: h, h: m, m: v, s: y, ms: w, Q: d }[s] || String(s || "").toLowerCase().replace(/s$/, "");
|
||||
}, u: function(s) {
|
||||
return s === void 0;
|
||||
} }, P = "en", x = {};
|
||||
x[P] = V;
|
||||
var Q = "$isDayjsObject", N = function(s) {
|
||||
return s instanceof z || !(!s || !s[Q]);
|
||||
}, U = function s(n, e, r) {
|
||||
var t;
|
||||
if (!n) return P;
|
||||
if (typeof n == "string") {
|
||||
var i = n.toLowerCase();
|
||||
x[i] && (t = i), e && (x[i] = e, t = i);
|
||||
var o = n.split("-");
|
||||
if (!t && o.length > 1) return s(o[0]);
|
||||
} else {
|
||||
var l = n.name;
|
||||
x[l] = n, t = l;
|
||||
}
|
||||
return !r && t && (P = t), t || !r && P;
|
||||
}, p = function(s, n) {
|
||||
if (N(s)) return s.clone();
|
||||
var e = typeof n == "object" ? n : {};
|
||||
return e.date = s, e.args = arguments, new z(e);
|
||||
}, a = K;
|
||||
a.l = U, a.i = N, a.w = function(s, n) {
|
||||
return p(s, { locale: n.$L, utc: n.$u, x: n.$x, $offset: n.$offset });
|
||||
};
|
||||
var z = function() {
|
||||
function s(e) {
|
||||
this.$L = U(e.locale, null, !0), this.parse(e), this.$x = this.$x || e.x || {}, this[Q] = !0;
|
||||
}
|
||||
var n = s.prototype;
|
||||
return n.parse = function(e) {
|
||||
this.$d = function(r) {
|
||||
var t = r.date, i = r.utc;
|
||||
if (t === null) return /* @__PURE__ */ new Date(NaN);
|
||||
if (a.u(t)) return /* @__PURE__ */ new Date();
|
||||
if (t instanceof Date) return new Date(t);
|
||||
if (typeof t == "string" && !/Z$/i.test(t)) {
|
||||
var o = t.match(I);
|
||||
if (o) {
|
||||
var l = o[2] - 1 || 0, $ = (o[7] || "0").substring(0, 3);
|
||||
return i ? new Date(Date.UTC(o[1], l, o[3] || 1, o[4] || 0, o[5] || 0, o[6] || 0, $)) : new Date(o[1], l, o[3] || 1, o[4] || 0, o[5] || 0, o[6] || 0, $);
|
||||
}
|
||||
}
|
||||
return new Date(t);
|
||||
}(e), this.init();
|
||||
}, n.init = function() {
|
||||
var e = this.$d;
|
||||
this.$y = e.getFullYear(), this.$M = e.getMonth(), this.$D = e.getDate(), this.$W = e.getDay(), this.$H = e.getHours(), this.$m = e.getMinutes(), this.$s = e.getSeconds(), this.$ms = e.getMilliseconds();
|
||||
}, n.$utils = function() {
|
||||
return a;
|
||||
}, n.isValid = function() {
|
||||
return this.$d.toString() !== c;
|
||||
}, n.isSame = function(e, r) {
|
||||
var t = p(e);
|
||||
return this.startOf(r) <= t && t <= this.endOf(r);
|
||||
}, n.isAfter = function(e, r) {
|
||||
return p(e) < this.startOf(r);
|
||||
}, n.isBefore = function(e, r) {
|
||||
return this.endOf(r) < p(e);
|
||||
}, n.$g = function(e, r, t) {
|
||||
return a.u(e) ? this[r] : this.set(t, e);
|
||||
}, n.unix = function() {
|
||||
return Math.floor(this.valueOf() / 1e3);
|
||||
}, n.valueOf = function() {
|
||||
return this.$d.getTime();
|
||||
}, n.startOf = function(e, r) {
|
||||
var t = this, i = !!a.u(r) || r, o = a.p(e), l = function(T, Y) {
|
||||
var j = a.w(t.$u ? Date.UTC(t.$y, Y, T) : new Date(t.$y, Y, T), t);
|
||||
return i ? j : j.endOf(D);
|
||||
}, $ = function(T, Y) {
|
||||
return a.w(t.toDate()[T].apply(t.toDate("s"), (i ? [0, 0, 0, 0] : [23, 59, 59, 999]).slice(Y)), t);
|
||||
}, M = this.$W, g = this.$M, k = this.$D, W = "set" + (this.$u ? "UTC" : "");
|
||||
switch (o) {
|
||||
case u:
|
||||
return i ? l(1, 0) : l(31, 11);
|
||||
case f:
|
||||
return i ? l(1, g) : l(0, g + 1);
|
||||
case b:
|
||||
var H = this.$locale().weekStart || 0, E = (M < H ? M + 7 : M) - H;
|
||||
return l(i ? k - E : k + (6 - E), g);
|
||||
case D:
|
||||
case h:
|
||||
return $(W + "Hours", 0);
|
||||
case m:
|
||||
return $(W + "Minutes", 1);
|
||||
case v:
|
||||
return $(W + "Seconds", 2);
|
||||
case y:
|
||||
return $(W + "Milliseconds", 3);
|
||||
default:
|
||||
return this.clone();
|
||||
}
|
||||
}, n.endOf = function(e) {
|
||||
return this.startOf(e, !1);
|
||||
}, n.$set = function(e, r) {
|
||||
var t, i = a.p(e), o = "set" + (this.$u ? "UTC" : ""), l = (t = {}, t[D] = o + "Date", t[h] = o + "Date", t[f] = o + "Month", t[u] = o + "FullYear", t[m] = o + "Hours", t[v] = o + "Minutes", t[y] = o + "Seconds", t[w] = o + "Milliseconds", t)[i], $ = i === D ? this.$D + (r - this.$W) : r;
|
||||
if (i === f || i === u) {
|
||||
var M = this.clone().set(h, 1);
|
||||
M.$d[l]($), M.init(), this.$d = M.set(h, Math.min(this.$D, M.daysInMonth())).$d;
|
||||
} else l && this.$d[l]($);
|
||||
return this.init(), this;
|
||||
}, n.set = function(e, r) {
|
||||
return this.clone().$set(e, r);
|
||||
}, n.get = function(e) {
|
||||
return this[a.p(e)]();
|
||||
}, n.add = function(e, r) {
|
||||
var t, i = this;
|
||||
e = Number(e);
|
||||
var o = a.p(r), l = function(g) {
|
||||
var k = p(i);
|
||||
return a.w(k.date(k.date() + Math.round(g * e)), i);
|
||||
};
|
||||
if (o === f) return this.set(f, this.$M + e);
|
||||
if (o === u) return this.set(u, this.$y + e);
|
||||
if (o === D) return l(1);
|
||||
if (o === b) return l(7);
|
||||
var $ = (t = {}, t[v] = O, t[m] = C, t[y] = L, t)[o] || 1, M = this.$d.getTime() + e * $;
|
||||
return a.w(M, this);
|
||||
}, n.subtract = function(e, r) {
|
||||
return this.add(-1 * e, r);
|
||||
}, n.format = function(e) {
|
||||
var r = this, t = this.$locale();
|
||||
if (!this.isValid()) return t.invalidDate || c;
|
||||
var i = e || "YYYY-MM-DDTHH:mm:ssZ", o = a.z(this), l = this.$H, $ = this.$m, M = this.$M, g = t.weekdays, k = t.months, W = t.meridiem, H = function(Y, j, A, F) {
|
||||
return Y && (Y[j] || Y(r, i)) || A[j].slice(0, F);
|
||||
}, E = function(Y) {
|
||||
return a.s(l % 12 || 12, Y, "0");
|
||||
}, T = W || function(Y, j, A) {
|
||||
var F = Y < 12 ? "AM" : "PM";
|
||||
return A ? F.toLowerCase() : F;
|
||||
};
|
||||
return i.replace(Z, function(Y, j) {
|
||||
return j || function(A) {
|
||||
switch (A) {
|
||||
case "YY":
|
||||
return String(r.$y).slice(-2);
|
||||
case "YYYY":
|
||||
return a.s(r.$y, 4, "0");
|
||||
case "M":
|
||||
return M + 1;
|
||||
case "MM":
|
||||
return a.s(M + 1, 2, "0");
|
||||
case "MMM":
|
||||
return H(t.monthsShort, M, k, 3);
|
||||
case "MMMM":
|
||||
return H(k, M);
|
||||
case "D":
|
||||
return r.$D;
|
||||
case "DD":
|
||||
return a.s(r.$D, 2, "0");
|
||||
case "d":
|
||||
return String(r.$W);
|
||||
case "dd":
|
||||
return H(t.weekdaysMin, r.$W, g, 2);
|
||||
case "ddd":
|
||||
return H(t.weekdaysShort, r.$W, g, 3);
|
||||
case "dddd":
|
||||
return g[r.$W];
|
||||
case "H":
|
||||
return String(l);
|
||||
case "HH":
|
||||
return a.s(l, 2, "0");
|
||||
case "h":
|
||||
return E(1);
|
||||
case "hh":
|
||||
return E(2);
|
||||
case "a":
|
||||
return T(l, $, !0);
|
||||
case "A":
|
||||
return T(l, $, !1);
|
||||
case "m":
|
||||
return String($);
|
||||
case "mm":
|
||||
return a.s($, 2, "0");
|
||||
case "s":
|
||||
return String(r.$s);
|
||||
case "ss":
|
||||
return a.s(r.$s, 2, "0");
|
||||
case "SSS":
|
||||
return a.s(r.$ms, 3, "0");
|
||||
case "Z":
|
||||
return o;
|
||||
}
|
||||
return null;
|
||||
}(Y) || o.replace(":", "");
|
||||
});
|
||||
}, n.utcOffset = function() {
|
||||
return 15 * -Math.round(this.$d.getTimezoneOffset() / 15);
|
||||
}, n.diff = function(e, r, t) {
|
||||
var i, o = this, l = a.p(r), $ = p(e), M = ($.utcOffset() - this.utcOffset()) * O, g = this - $, k = function() {
|
||||
return a.m(o, $);
|
||||
};
|
||||
switch (l) {
|
||||
case u:
|
||||
i = k() / 12;
|
||||
break;
|
||||
case f:
|
||||
i = k();
|
||||
break;
|
||||
case d:
|
||||
i = k() / 3;
|
||||
break;
|
||||
case b:
|
||||
i = (g - M) / 6048e5;
|
||||
break;
|
||||
case D:
|
||||
i = (g - M) / 864e5;
|
||||
break;
|
||||
case m:
|
||||
i = g / C;
|
||||
break;
|
||||
case v:
|
||||
i = g / O;
|
||||
break;
|
||||
case y:
|
||||
i = g / L;
|
||||
break;
|
||||
default:
|
||||
i = g;
|
||||
}
|
||||
return t ? i : a.a(i);
|
||||
}, n.daysInMonth = function() {
|
||||
return this.endOf(f).$D;
|
||||
}, n.$locale = function() {
|
||||
return x[this.$L];
|
||||
}, n.locale = function(e, r) {
|
||||
if (!e) return this.$L;
|
||||
var t = this.clone(), i = U(e, r, !0);
|
||||
return i && (t.$L = i), t;
|
||||
}, n.clone = function() {
|
||||
return a.w(this.$d, this);
|
||||
}, n.toDate = function() {
|
||||
return new Date(this.valueOf());
|
||||
}, n.toJSON = function() {
|
||||
return this.isValid() ? this.toISOString() : null;
|
||||
}, n.toISOString = function() {
|
||||
return this.$d.toISOString();
|
||||
}, n.toString = function() {
|
||||
return this.$d.toUTCString();
|
||||
}, s;
|
||||
}(), J = z.prototype;
|
||||
return p.prototype = J, [["$ms", w], ["$s", y], ["$m", v], ["$H", m], ["$W", D], ["$M", f], ["$y", u], ["$D", h]].forEach(function(s) {
|
||||
J[s[1]] = function(n) {
|
||||
return this.$g(n, s[0], s[1]);
|
||||
};
|
||||
}), p.extend = function(s, n) {
|
||||
return s.$i || (s(n, z, p), s.$i = !0), p;
|
||||
}, p.locale = U, p.isDayjs = N, p.unix = function(s) {
|
||||
return p(1e3 * s);
|
||||
}, p.en = x[P], p.Ls = x, p.p = {}, p;
|
||||
});
|
||||
})(dayjs_min);
|
||||
var dayjs_minExports = dayjs_min.exports;
|
||||
const dayjs = /* @__PURE__ */ getDefaultExportFromCjs(dayjs_minExports);
|
||||
var zhCn = { exports: {} };
|
||||
(function(_, S) {
|
||||
(function(L, O) {
|
||||
_.exports = O(dayjs_minExports);
|
||||
})(commonjsGlobal, function(L) {
|
||||
function O(y) {
|
||||
return y && typeof y == "object" && "default" in y ? y : { default: y };
|
||||
}
|
||||
var C = O(L), w = { name: "zh-cn", weekdays: "星期日_星期一_星期二_星期三_星期四_星期五_星期六".split("_"), weekdaysShort: "周日_周一_周二_周三_周四_周五_周六".split("_"), weekdaysMin: "日_一_二_三_四_五_六".split("_"), months: "一月_二月_三月_四月_五月_六月_七月_八月_九月_十月_十一月_十二月".split("_"), monthsShort: "1月_2月_3月_4月_5月_6月_7月_8月_9月_10月_11月_12月".split("_"), ordinal: function(y, v) {
|
||||
return v === "W" ? y + "周" : y + "日";
|
||||
}, weekStart: 1, yearStart: 4, formats: { LT: "HH:mm", LTS: "HH:mm:ss", L: "YYYY/MM/DD", LL: "YYYY年M月D日", LLL: "YYYY年M月D日Ah点mm分", LLLL: "YYYY年M月D日ddddAh点mm分", l: "YYYY/M/D", ll: "YYYY年M月D日", lll: "YYYY年M月D日 HH:mm", llll: "YYYY年M月D日dddd HH:mm" }, relativeTime: { future: "%s内", past: "%s前", s: "几秒", m: "1 分钟", mm: "%d 分钟", h: "1 小时", hh: "%d 小时", d: "1 天", dd: "%d 天", M: "1 个月", MM: "%d 个月", y: "1 年", yy: "%d 年" }, meridiem: function(y, v) {
|
||||
var m = 100 * y + v;
|
||||
return m < 600 ? "凌晨" : m < 900 ? "早上" : m < 1100 ? "上午" : m < 1300 ? "中午" : m < 1800 ? "下午" : "晚上";
|
||||
} };
|
||||
return C.default.locale(w, null, !0), w;
|
||||
});
|
||||
})(zhCn);
|
||||
dayjs.locale("zh-cn");
|
||||
const createWorkerSide = (_) => {
|
||||
const S = {
|
||||
msg_id: 0,
|
||||
fn_id: 0,
|
||||
mQueue: /* @__PURE__ */ new Map(),
|
||||
fnStore: /* @__PURE__ */ new Map(),
|
||||
mMethodWorkspace: /* @__PURE__ */ new Map()
|
||||
};
|
||||
return { Util: (w, y) => {
|
||||
const v = (f) => {
|
||||
if (typeof f != "function") return f;
|
||||
const d = `fn_${S.fn_id++}`;
|
||||
return S.fnStore.set(d, f), { __fn_id__: d };
|
||||
}, m = (f, d, u) => {
|
||||
const h = S.msg_id++;
|
||||
return new Promise((c, I) => {
|
||||
S.mQueue.set(h, [c, I]), _.postMessage({ id: h, task_id: w, session: y, category: "util", method: f, args: d.map(v), chain_id: u });
|
||||
});
|
||||
}, D = (f) => new Proxy(f, {
|
||||
get(d, u) {
|
||||
return typeof u == "symbol" ? d[u] : u in Promise.prototype ? (...h) => D(
|
||||
d.then((c) => c && typeof c == "object" && c.__chain_id__ ? m(u, h, c.__chain_id__) : u === "then" ? typeof h[0] == "function" ? h[0](c) : c : (u === "finally" && typeof h[0] == "function" && h[0](), c)).catch((c) => u === "catch" ? typeof h[0] == "function" ? h[0](c) : Promise.reject(c) : u === "finally" ? (typeof h[0] == "function" && h[0](), Promise.reject(c)) : u === "then" && typeof h[1] == "function" ? h[1](c) : Promise.reject(c))
|
||||
) : (...h) => D(
|
||||
d.then((c) => {
|
||||
if (!c || !c.__chain_id__) throw new Error(`${u} 不可链式调用`);
|
||||
return m(u, h, c.__chain_id__);
|
||||
})
|
||||
);
|
||||
}
|
||||
}), b = (f) => new Proxy(
|
||||
(...d) => D(m(f, d, null)),
|
||||
{ get: (d, u) => typeof u == "symbol" ? void 0 : b(`${f}/${u}`) }
|
||||
);
|
||||
return new Proxy(() => {
|
||||
}, {
|
||||
// 函数调用形式:Util('ns')
|
||||
apply: (f, d, [u]) => new Proxy({}, {
|
||||
get: (h, c) => typeof c == "symbol" ? void 0 : b(u ? `${u}/${c}` : c)
|
||||
}),
|
||||
// 属性访问形式:Util.fn
|
||||
get: (f, d) => typeof d == "symbol" ? void 0 : b(d)
|
||||
});
|
||||
}, state: S, onMessage: ({ data: { id: w, data: y, error: v, category: m, fn_id: D, cb_id: b, args: f } }) => {
|
||||
if (m === "util") {
|
||||
const d = S.mQueue.get(w);
|
||||
if (!d) return;
|
||||
S.mQueue.delete(w), v !== null ? d[1](v) : d[0](y);
|
||||
} else if (m === "invoke_fn") {
|
||||
const d = S.fnStore.get(D);
|
||||
if (!d) {
|
||||
_.postMessage({ category: "invoke_fn_result", cb_id: b, result: null, error: `fn_id '${D}' not found` });
|
||||
return;
|
||||
}
|
||||
Promise.resolve(d(...f || [])).then((u) => {
|
||||
_.postMessage({ category: "invoke_fn_result", cb_id: b, result: u, error: null });
|
||||
}).catch((u) => {
|
||||
_.postMessage({ category: "invoke_fn_result", cb_id: b, result: null, error: String(u) });
|
||||
});
|
||||
}
|
||||
}, clearSession: (w) => {
|
||||
S.mMethodWorkspace.delete(w);
|
||||
} };
|
||||
};
|
||||
if (typeof self < "u" && (self !== globalThis || typeof DedicatedWorkerGlobalScope < "u" && self instanceof DedicatedWorkerGlobalScope)) {
|
||||
const { Util, onMessage, clearSession } = createWorkerSide(self);
|
||||
self.addEventListener("message", onMessage), self.addEventListener("message", ({ data: _ }) => {
|
||||
_.category === "clear" && clearSession(_.session);
|
||||
}), self.addEventListener("message", ({ data: { id, method, args, category, session } }) => {
|
||||
if (category !== "eval") return;
|
||||
const argsKeys = Object.keys(args || {}), fn = eval(`(async function({${argsKeys.join(", ")}}, Util){ ${method} })`);
|
||||
Promise.resolve(fn.call({}, args || {}, Util(id, session))).then((_) => self.postMessage({ id, session, category, data: _, error: null })).catch((_) => {
|
||||
const S = _ instanceof DOMException && _.name === "DataCloneError" ? "DataCloneError 返回结果只能为简单数据格式" : String(_);
|
||||
self.postMessage({ id, session, category, data: null, error: S });
|
||||
});
|
||||
});
|
||||
}
|
||||
export {
|
||||
createWorkerSide
|
||||
};
|
||||
Reference in New Issue
Block a user