From 766d499b0ffad5fddca1071ab85d504d4bad8f33 Mon Sep 17 00:00:00 2001 From: iapyang Date: Mon, 27 Jun 2022 14:27:13 +0800 Subject: [PATCH 01/18] =?UTF-8?q?feat:=20=E6=96=B0=E5=A2=9Epromise?= =?UTF-8?q?=E5=88=A4=E6=96=AD=E6=96=B9=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/core/2.base.js | 4 ++++ typescript/core/base.ts | 6 ++++++ 2 files changed, 10 insertions(+) diff --git a/src/core/2.base.js b/src/core/2.base.js index 4ab530629..7071eb3e1 100644 --- a/src/core/2.base.js +++ b/src/core/2.base.js @@ -524,6 +524,10 @@ isWindow: function (obj) { return obj != null && obj == obj.window; + }, + + isPromise: function(obj) { + return !!obj && (BI.isObject(obj) || BI.isFunction(obj)) && BI.isFunction(obj.then); } }); diff --git a/typescript/core/base.ts b/typescript/core/base.ts index a30828489..1d6022a1c 100644 --- a/typescript/core/base.ts +++ b/typescript/core/base.ts @@ -376,6 +376,12 @@ export interface _base { getDate: (...args: (number | string)[]) => Date; getTime: (...args: any[]) => number; + + /** + * 判断一个对象是不是promise + * @param obj 对象 + */ + isPromise: (obj: any) => obj is Promise; } type merge = { From d0aaadb32b96ff940846df38d032bedef1fbd263 Mon Sep 17 00:00:00 2001 From: iapyang Date: Mon, 27 Jun 2022 14:53:08 +0800 Subject: [PATCH 02/18] =?UTF-8?q?chore:=20=E6=9B=B4=E6=96=B0=E4=BE=9D?= =?UTF-8?q?=E8=B5=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index 71caf2276..b24b569d2 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,7 @@ "@babel/core": "^7.17.4", "@babel/polyfill": "7.6.0", "@fui/babel-preset-fineui": "^2.0.0", - "@fui/eslint-plugin": "1.0.11", + "@fui/eslint-plugin": "1.0.14", "@types/node": "15.6.1", "autoprefixer": "9.6.1", "babel-loader": "8.0.6", @@ -47,7 +47,7 @@ "source-map-loader": "0.2.4", "style-loader": "0.23.1", "terser-webpack-plugin": "4.2.3", - "typescript": "3.5.2", + "typescript": "3.9.2", "webpack": "4.35.2", "webpack-cli": "3.3.5", "webpack-dev-server": "3.7.2", @@ -82,4 +82,4 @@ }, "author": "fanruan", "license": "MIT" -} \ No newline at end of file +} From 4465d0a4415d536060ebade6cec9f225a44164a1 Mon Sep 17 00:00:00 2001 From: iapyang Date: Mon, 27 Jun 2022 16:50:58 +0800 Subject: [PATCH 03/18] =?UTF-8?q?feat:=20=E6=A1=86=E6=9E=B6=E4=B8=89?= =?UTF-8?q?=E5=A4=A7=E4=BB=B6=E5=86=99=E5=AE=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- typescript/core/worker/worker.channel.ts | 200 ++++++++++++++++++++ typescript/core/worker/worker.controller.ts | 131 +++++++++++++ typescript/core/worker/worker.ts | 32 ++++ 3 files changed, 363 insertions(+) create mode 100644 typescript/core/worker/worker.channel.ts create mode 100644 typescript/core/worker/worker.controller.ts create mode 100644 typescript/core/worker/worker.ts diff --git a/typescript/core/worker/worker.channel.ts b/typescript/core/worker/worker.channel.ts new file mode 100644 index 000000000..0f0253121 --- /dev/null +++ b/typescript/core/worker/worker.channel.ts @@ -0,0 +1,200 @@ +import { IWorkerController, WorkerMessageType, IWorkerMessage } from './worker'; + +const COMMUNICATION_TIMEOUT = 30000; + +/** + * 通信通道 + */ +export class WorkerChannel { + /** + * Web Worker 实例 + */ + private worker: Worker; + + /** + * 上层通信控制器 + */ + private controller: IWorkerController; + + /** + * 会话响应器 Map + */ + private sessionHandlerMap: { + [propsName: string]: Function; + }; + + public constructor(worker: Worker, controller: IWorkerController) { + this.worker = worker; + this.controller = controller; + + this.sessionHandlerMap = {}; + + // 绑定 worker onmessage 事件的回调 + this.worker.addEventListener('message', this.onmessage.bind(this)); + } + + /** + * 发送响应 + * + * @param sessionId 会话 Id + * @param payload 负载 + */ + public response(sessionId: string, actionType: string, payload: any): void { + this.postMessage({ + messageType: WorkerMessageType.REPLY, + actionType, + payload, + sessionId, + }); + } + + /** + * 发送请求, 不等待响应 + * + * @param actionType 事务类型 + * @param payload 负载 + */ + public request(actionType: string, payload: any): void { + const sessionId = this.generateSessionId(); + this.postMessage({ + messageType: WorkerMessageType.REQUEST, + actionType, + payload, + sessionId, + }); + + // 不等待结果, 还会收到响应, 添加个空的会话响应器 + this.addSessionHandler(sessionId, () => {}); + } + + /** + * 发送请求, 并等待响应 + * + * @param actionType 事务类型 + * @param payload 负载 + * @param timeout 响应超时 + * @returns {Promise} 等待响应的 Promise + */ + public requestPromise(actionType: string, payload: any, timeout = COMMUNICATION_TIMEOUT): Promise { + const sessionId = this.generateSessionId(); + const message = { + messageType: WorkerMessageType.REQUEST, + actionType, + payload, + sessionId, + }; + + // 请求封装为一个 Promise, 等待会话响应器进行 resolve + const PromiseFunction = (resolve: Function, reject: Function): any => { + // 启动请求超时计时器 + const timeoutHandler = setTimeout(() => { + clearTimeout(timeoutHandler); + + reject(); + }, timeout); + + const sessionHandler: Function = (message: IWorkerMessage) => { + // 会话回调函数, 开始处理响应 + this.deleteSessionHandler(message.sessionId); + clearTimeout(timeoutHandler); + + resolve(message.payload); + }; + + this.addSessionHandler(sessionId, sessionHandler); + + // 开始发送请求 + this.postMessage(message); + }; + + return new Promise(PromiseFunction); + } + + /** + * 收到会话消息的处理函数 + * + * 发现是请求, 调用通信控制器的事务处理器进行处理, 获取事务结果并响应; + * 发现是响应,调用会话响应器 + * @param event worker 通信事件 + */ + private onmessage(event: { data: IWorkerMessage }): void { + const { data: message } = event; + const { messageType, sessionId, actionType } = message; + + // 接收到请求 + if (messageType === WorkerMessageType.REQUEST) { + // 处理请求 + this.controller.actionHandler(message) + .then(actionResult => { + // 响应请求 + this.response(sessionId, actionType, actionResult); + }); + } + + // 接收到响应 + if (messageType === WorkerMessageType.REPLY) { + // 处理响应 + if (this.hasSessionHandler(sessionId)) { + this.sessionHandlerMap[sessionId](message); + } else { + throw new Error(`Session \`${sessionId}\` handler no exist`); + } + } + } + + /** + * 封装的 worker 原生 postMessage 接口 + * 支持 structured clone 和 transfer 2种通信模式 + * + * @param message 会话消息 + */ + private postMessage(message: IWorkerMessage): void { + this.worker.postMessage(message); + } + + /** + * 添加会话响应器 + * + * @param sessionId 会话 Id + * @param handler 会话响应器 + */ + private addSessionHandler(sessionId: string, handler: Function): void { + if (!this.hasSessionHandler(sessionId)) { + this.sessionHandlerMap[sessionId] = handler; + } else { + throw new Error(`SessionId \`${sessionId}\` already exist!`); + } + } + + /** + * 移除会话响应器 + * + * @param sessionId + */ + private deleteSessionHandler(sessionId: string): void { + if (this.hasSessionHandler(sessionId)) { + delete this.sessionHandlerMap[sessionId]; + } + } + + /** + * 生成每次独立会话的 Id + * + * @returns 会话 Id + */ + private generateSessionId(): string { + const sessionId = `w_${BI.UUID()}`; + + return sessionId; + } + + /** + * 判断是否有指定会话的处理器 + * + * @param sessionId 会话 Id + * @returns {boolean} 判断结果 + */ + private hasSessionHandler(sessionId: string): boolean { + return !!this.sessionHandlerMap[sessionId]; + } +} diff --git a/typescript/core/worker/worker.controller.ts b/typescript/core/worker/worker.controller.ts new file mode 100644 index 000000000..ebc0d16f6 --- /dev/null +++ b/typescript/core/worker/worker.controller.ts @@ -0,0 +1,131 @@ +import type { IWorkerController, IWorkerMessage } from './worker'; +import { WorkerChannel } from './worker.channel'; + +/** + * 通信控制器 + * + * @class BaseWorkerController + */ +export default class BaseWorkerController implements IWorkerController { + /** + * 原生 worker, 在子类中实例化 + */ + protected worker: Worker; + + /** + * 通信 Channel, 在子类中实例化 + */ + protected channel: WorkerChannel; + + /** + * 事务处理器 Map + */ + protected actionHandlerMap: { + [propsName: string]: (payload: any) => any; + }; + + public constructor() { + this.actionHandlerMap = {}; + } + + /** + * 发送事务,不等待结果 + * + * @param actionType 事务类型 + * @param payload 负载 + */ + public request(actionType: string, payload: any): void { + if (this.channel) { + return this.channel.request(actionType, payload); + } + + console.error('No channel.'); + + return; + } + + /** + * 发送 Promise 形式的事务, 在 then 中获取响应 + * + * @param actionType 事务类型 + * @param payload 负载 + * @param [timeout] 响应的超时; Worker 通道是可靠的, 超时后只上报, 不阻止当前请求 + */ + public requestPromise(actionType: string, payload: any = '', timeout?: number): Promise { + // 有 Channel 实例才能进行通信, 此时还没有实例化是浏览器不支持创建 worker + if (this.channel) { + return this.channel.requestPromise(actionType, payload, timeout); + } + + // 兼容上层调用的 .then().catch() + return Promise.reject(new Error('No channel.')); + } + + /** + * 添加事务处理器, 不允许重复添加 + * + * @param actionType 事务类型 + * @param handler 事务处理器 + */ + public addActionHandler(actionType: string, handler: (payload: any) => any): void { + if (this.hasActionHandler(actionType)) { + throw new Error(`Add action \`${actionType}\` handler repeat`); + } + this.actionHandlerMap[actionType] = handler; + } + + /** + * 事务处理器, 提供给通信 Channel 调用 + * + * @param message 会话消息 + * @returns + */ + public actionHandler(message: IWorkerMessage): Promise { + const { actionType, payload } = message; + + if (this.hasActionHandler(actionType)) { + // 执行指定的事务处理器, 并返回 Promise 封装的事务结果 + try { + const actionResult = this.actionHandlerMap[actionType](payload); + + // 对于 Promise 形式的结果, 需要进行 Promise 错误捕获 + if (BI.isPromise(actionResult)) { + return actionResult.catch(error => Promise.reject(error)); + } + + // 对数据结果, 包装为 Promise + return Promise.resolve(actionResult); + } catch (error) { + // 继续抛出给外层 + return Promise.reject(error); + } + } else { + throw new Error(`Not Found Session Handler \`${actionType}\`.`); + } + } + + /** + * 添加 worker onmessage 事件的回调 + * + * @param {(event: any) => void} onmessage 回调函数 + * @returns {() => void} 移除监听函数 + */ + public addOnmessageListener(onmessage: (event: any) => void): () => void { + this.worker.addEventListener('message', onmessage); + + // 返回移除监听函数 + return () => { + this.worker.removeEventListener('message', onmessage); + }; + } + + /** + * 判断是否有指定事务的处理器 + * + * @param actionType 事务类型 + * @returns {boolean} + */ + protected hasActionHandler(actionType: string): boolean { + return !!this.actionHandlerMap[actionType]; + } +} diff --git a/typescript/core/worker/worker.ts b/typescript/core/worker/worker.ts new file mode 100644 index 000000000..24e5825a4 --- /dev/null +++ b/typescript/core/worker/worker.ts @@ -0,0 +1,32 @@ +/** + * 会话消息类型枚举 + */ +export const enum WorkerMessageType { + REQUEST = 'REQUEST', + REPLY = 'REPLY', +} + +/** + * 会话消息 + */ +export interface IWorkerMessage { + messageType: WorkerMessageType; + actionType: string; + sessionId: string; + + /** + * 数据交换参数 + */ + payload: any; +} + +/** + * 通信控制器需要实现的 interface + */ +export interface IWorkerController { + + /** + * 事务处理器 + */ + actionHandler: (message: IWorkerMessage) => Promise; +} From 6621593bf08700bf81263bee189ad545288efb6e Mon Sep 17 00:00:00 2001 From: iapyang Date: Mon, 27 Jun 2022 19:07:21 +0800 Subject: [PATCH 04/18] =?UTF-8?q?refactor:=20=E6=94=B9=E4=B8=AA=E5=90=8D?= =?UTF-8?q?=E5=AD=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- typescript/core/worker/worker.channel.ts | 2 +- typescript/core/worker/worker.controller.ts | 2 +- typescript/core/worker/{worker.ts => worker.utils.ts} | 0 3 files changed, 2 insertions(+), 2 deletions(-) rename typescript/core/worker/{worker.ts => worker.utils.ts} (100%) diff --git a/typescript/core/worker/worker.channel.ts b/typescript/core/worker/worker.channel.ts index 0f0253121..2d2fcc116 100644 --- a/typescript/core/worker/worker.channel.ts +++ b/typescript/core/worker/worker.channel.ts @@ -1,4 +1,4 @@ -import { IWorkerController, WorkerMessageType, IWorkerMessage } from './worker'; +import { IWorkerController, WorkerMessageType, IWorkerMessage } from './worker.utils'; const COMMUNICATION_TIMEOUT = 30000; diff --git a/typescript/core/worker/worker.controller.ts b/typescript/core/worker/worker.controller.ts index ebc0d16f6..cba5f57ee 100644 --- a/typescript/core/worker/worker.controller.ts +++ b/typescript/core/worker/worker.controller.ts @@ -1,4 +1,4 @@ -import type { IWorkerController, IWorkerMessage } from './worker'; +import type { IWorkerController, IWorkerMessage } from './worker.utils'; import { WorkerChannel } from './worker.channel'; /** diff --git a/typescript/core/worker/worker.ts b/typescript/core/worker/worker.utils.ts similarity index 100% rename from typescript/core/worker/worker.ts rename to typescript/core/worker/worker.utils.ts From 5d35ab39a6902ba3e9495e9d51912f5b55503cd7 Mon Sep 17 00:00:00 2001 From: iapyang Date: Mon, 27 Jun 2022 19:15:44 +0800 Subject: [PATCH 05/18] =?UTF-8?q?refactor:=20=E7=BB=9F=E4=B8=80=E4=B8=8B?= =?UTF-8?q?=E5=90=8D=E5=AD=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- typescript/core/worker/worker.controller.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/typescript/core/worker/worker.controller.ts b/typescript/core/worker/worker.controller.ts index cba5f57ee..2613d2c45 100644 --- a/typescript/core/worker/worker.controller.ts +++ b/typescript/core/worker/worker.controller.ts @@ -4,9 +4,9 @@ import { WorkerChannel } from './worker.channel'; /** * 通信控制器 * - * @class BaseWorkerController + * @class WorkerBaseController */ -export default class BaseWorkerController implements IWorkerController { +export default class WorkerBaseController implements IWorkerController { /** * 原生 worker, 在子类中实例化 */ From 50fc71eeb6891f3781f2af28114f23301b62b2ef Mon Sep 17 00:00:00 2001 From: iapyang Date: Tue, 28 Jun 2022 19:35:25 +0800 Subject: [PATCH 06/18] =?UTF-8?q?chore:=20=E6=9B=B4=E6=96=B0=E7=89=88?= =?UTF-8?q?=E6=9C=AC=E5=8F=B7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index b24b569d2..878b923a3 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,7 @@ "@babel/core": "^7.17.4", "@babel/polyfill": "7.6.0", "@fui/babel-preset-fineui": "^2.0.0", - "@fui/eslint-plugin": "1.0.14", + "@fui/eslint-plugin": "1.0.15", "@types/node": "15.6.1", "autoprefixer": "9.6.1", "babel-loader": "8.0.6", From fd280e15ebce802062ca8a2f27adeb62e1bf66ae Mon Sep 17 00:00:00 2001 From: iapyang Date: Tue, 28 Jun 2022 19:36:12 +0800 Subject: [PATCH 07/18] =?UTF-8?q?refactor:=20=E6=94=B9=E5=90=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../{worker.utils.ts => worker.core.ts} | 26 +++++++++++++++---- 1 file changed, 21 insertions(+), 5 deletions(-) rename typescript/core/worker/{worker.utils.ts => worker.core.ts} (57%) diff --git a/typescript/core/worker/worker.utils.ts b/typescript/core/worker/worker.core.ts similarity index 57% rename from typescript/core/worker/worker.utils.ts rename to typescript/core/worker/worker.core.ts index 24e5825a4..0c6db6186 100644 --- a/typescript/core/worker/worker.utils.ts +++ b/typescript/core/worker/worker.core.ts @@ -1,16 +1,16 @@ /** * 会话消息类型枚举 */ -export const enum WorkerMessageType { - REQUEST = 'REQUEST', - REPLY = 'REPLY', -} +export const WorkerMessageType = { + REQUEST: 'REQUEST', + REPLY: 'REPLY', +}; /** * 会话消息 */ export interface IWorkerMessage { - messageType: WorkerMessageType; + messageType: string; actionType: string; sessionId: string; @@ -30,3 +30,19 @@ export interface IWorkerController { */ actionHandler: (message: IWorkerMessage) => Promise; } + +/** + * Worker创建配置 + */ +export interface IWorkerOptions { + + /** + * worker 资源 url + */ + workerUrl: string; + + /** + * worker 实例名称 + */ + workerName: string; +} From 3d10e36001c777f9a940b92af3fb772070f00950 Mon Sep 17 00:00:00 2001 From: iapyang Date: Tue, 28 Jun 2022 19:36:38 +0800 Subject: [PATCH 08/18] =?UTF-8?q?refactor:=20=E7=A7=BB=E5=8A=A8=E4=B8=AA?= =?UTF-8?q?=E4=BD=8D=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- typescript/core/worker/{ => controller}/worker.controller.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) rename typescript/core/worker/{ => controller}/worker.controller.ts (96%) diff --git a/typescript/core/worker/worker.controller.ts b/typescript/core/worker/controller/worker.controller.ts similarity index 96% rename from typescript/core/worker/worker.controller.ts rename to typescript/core/worker/controller/worker.controller.ts index 2613d2c45..10c89f8c7 100644 --- a/typescript/core/worker/worker.controller.ts +++ b/typescript/core/worker/controller/worker.controller.ts @@ -1,5 +1,5 @@ -import type { IWorkerController, IWorkerMessage } from './worker.utils'; -import { WorkerChannel } from './worker.channel'; +import type { IWorkerController, IWorkerMessage } from '../worker.core'; +import { WorkerChannel } from '../worker.channel'; /** * 通信控制器 From b458896938d884823563e4ca68ecd2efa580aff6 Mon Sep 17 00:00:00 2001 From: iapyang Date: Tue, 28 Jun 2022 19:38:38 +0800 Subject: [PATCH 09/18] =?UTF-8?q?refactor:=20=E6=94=B9=E5=90=8D=E6=9B=B4?= =?UTF-8?q?=E6=8D=A2=E4=BD=8D=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- typescript/core/worker/worker.channel.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/typescript/core/worker/worker.channel.ts b/typescript/core/worker/worker.channel.ts index 2d2fcc116..a39124a14 100644 --- a/typescript/core/worker/worker.channel.ts +++ b/typescript/core/worker/worker.channel.ts @@ -1,4 +1,4 @@ -import { IWorkerController, WorkerMessageType, IWorkerMessage } from './worker.utils'; +import { IWorkerController, WorkerMessageType, IWorkerMessage } from './worker.core'; const COMMUNICATION_TIMEOUT = 30000; From 66e0e4e426d04059a216575ecc7261a77a7aaa56 Mon Sep 17 00:00:00 2001 From: iapyang Date: Tue, 28 Jun 2022 19:59:19 +0800 Subject: [PATCH 10/18] =?UTF-8?q?feat:=20=E6=96=B0=E5=A2=9E=E5=90=84?= =?UTF-8?q?=E7=BA=BF=E7=A8=8Bcontroller?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- types/globals.d.ts | 2 + .../worker.main_thread.controller.ts | 85 +++++++++++++++++++ .../worker.worker_thread.controller.ts | 21 +++++ 3 files changed, 108 insertions(+) create mode 100644 typescript/core/worker/controller/worker.main_thread.controller.ts create mode 100644 typescript/core/worker/controller/worker.worker_thread.controller.ts diff --git a/types/globals.d.ts b/types/globals.d.ts index f35336db4..0b5805aa0 100644 --- a/types/globals.d.ts +++ b/types/globals.d.ts @@ -11,3 +11,5 @@ declare const Fix: Obj; declare interface String { replaceAll(regx: string, callback: (str: string) => void): string; } + +declare const _global: typeof window; diff --git a/typescript/core/worker/controller/worker.main_thread.controller.ts b/typescript/core/worker/controller/worker.main_thread.controller.ts new file mode 100644 index 000000000..6cabc8380 --- /dev/null +++ b/typescript/core/worker/controller/worker.main_thread.controller.ts @@ -0,0 +1,85 @@ +import { WorkerChannel } from "../worker.channel"; +import type { IWorkerOptions } from "../worker.core"; +import WorkerBaseController from "./worker.controller"; + +export class WorkerMainThreadController extends WorkerBaseController { + /** + * 浏览器是否实现了 HTML 规范的 Worker Class + */ + public static hasWorkerClass = !!_global.Worker; + + /** + * 是否支持 new Worker, 默认为 Wroker Class 是否实现 + */ + + public canNewWorker: boolean = WorkerMainThreadController.hasWorkerClass; + + /** + * 主线程 new Worker 起始时刻 + */ + public timeBeforeNewWorker: number; + + /** + * 主线程 new Worker 完毕时刻 + */ + public timeAfterNewWorker: number; + + public constructor(options: IWorkerOptions) { + super(); + + if (!this.canNewWorker) { + // 都没有 Worker Class, 没法继续了 + return; + } + + this.newWorker(options); + } + + /** + * 销毁 Worker 线程实例 + */ + public terminate(): void { + this.worker?.terminate(); + } + + protected reportActionHandlerError(actionType: string, error: any): void { + console.error(`Worker aciton ${actionType}:`, error); + + // 主线程的报错, 在 window.onerror 中可以拿到报错堆栈, 直接抛出即可 + throw new Error(error); + } + + /** + * 创建 Worker 线程实例 + */ + private newWorker(options: IWorkerOptions) { + this.timeBeforeNewWorker = Date.now(); + + try { + // 主线程通过 new Worker() 获取 Worker 实例 + this.worker = new Worker(options.workerUrl, { + name: options.workerName, + }); + + /** + * 监控和上报 worker 中的报错 + * window.onerror 中也能监控到 worker.onerror( Worker 运行报错) + */ + this.worker.onerror = (error): void => { + console.error('Worker onerror:', error); + }; + + this.timeAfterNewWorker = Date.now(); + + // 实例化 Channel + this.channel = new WorkerChannel(this.worker, { + actionHandler: this.actionHandler.bind(this), + }); + } catch (error) { + console.error('Init worker fail:', error); + + // 创建 worker 失败, 标识改为不支持 + this.canNewWorker = false; + } + } +} diff --git a/typescript/core/worker/controller/worker.worker_thread.controller.ts b/typescript/core/worker/controller/worker.worker_thread.controller.ts new file mode 100644 index 000000000..71abef337 --- /dev/null +++ b/typescript/core/worker/controller/worker.worker_thread.controller.ts @@ -0,0 +1,21 @@ +import { WorkerChannel } from "../worker.channel"; +import WorkerBaseController from "./worker.controller"; + +export class WorkerThreadController extends WorkerBaseController { + public constructor() { + super(); + + // Worker 线程中的全局环境 self 就是 Worker 实例 + this.worker = self as any; + this.channel = new WorkerChannel(this.worker, { + actionHandler: this.actionHandler.bind(this), + }); + } + + protected reportActionHandlerError(actionType: string, error: any): void { + console.error(`Worker aciton ${actionType}:`, error); + + // 正常抛出 + throw new Error(error); + } +} From 0ee6430c63233c2ee23ab03d510ce5768d6c39bb Mon Sep 17 00:00:00 2001 From: iapyang Date: Tue, 28 Jun 2022 19:59:58 +0800 Subject: [PATCH 11/18] =?UTF-8?q?feat:=20action=E5=9F=BA=E7=A1=80=E7=B1=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../core/worker/action/worker.action.ts | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 typescript/core/worker/action/worker.action.ts diff --git a/typescript/core/worker/action/worker.action.ts b/typescript/core/worker/action/worker.action.ts new file mode 100644 index 000000000..6a581fd9f --- /dev/null +++ b/typescript/core/worker/action/worker.action.ts @@ -0,0 +1,28 @@ +import type WorkerBaseController from "../controller/worker.controller"; + +/** + * 事务的基类 + */ +export default abstract class WorkerBaseAction { + /** + * 通信控制器 + */ + protected controller: WorkerBaseController; + + /** + * 线程上的 action 集合, 用于调用其他命名空间下的事务 + */ + protected threadAction: any; + + public constructor(controller: WorkerBaseController, threadAction: any) { + this.controller = controller; + this.threadAction = threadAction; + + this.addActionHandler(); + } + + /** + * 添加事务的处理器 + */ + protected abstract addActionHandler(): void; +} From 9a5d7694ec079bd753097d3212801c761dc3d061 Mon Sep 17 00:00:00 2001 From: iapyang Date: Tue, 28 Jun 2022 20:01:27 +0800 Subject: [PATCH 12/18] =?UTF-8?q?feat:=20=E4=B8=BB=E6=AC=A1=E7=BA=BF?= =?UTF-8?q?=E7=A8=8B=E5=85=A5=E5=8F=A3abstract=E7=B1=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- typescript/core/worker/worker.main_thread.ts | 40 +++++++++++++++++++ .../core/worker/worker.worker_thread.ts | 17 ++++++++ 2 files changed, 57 insertions(+) create mode 100644 typescript/core/worker/worker.main_thread.ts create mode 100644 typescript/core/worker/worker.worker_thread.ts diff --git a/typescript/core/worker/worker.main_thread.ts b/typescript/core/worker/worker.main_thread.ts new file mode 100644 index 000000000..3140bbbe7 --- /dev/null +++ b/typescript/core/worker/worker.main_thread.ts @@ -0,0 +1,40 @@ +import { WorkerMainThreadController } from "./controller/worker.main_thread.controller"; +import { IWorkerOptions } from "./worker.core"; + +/** + * 主线程Worker + */ +export abstract class MainThreadWorker { + /** + * Worker 名称 + */ + public name: string; + + /** + * 主线程通信控制器 + */ + public controller: WorkerMainThreadController; + + /** + * 是否已经终止掉 Worker + */ + protected isTerminated = false; + + public constructor(options: IWorkerOptions) { + this.name = options.workerName; + this.controller = new WorkerMainThreadController(options); + this.initActions(); + } + + protected abstract initActions(): void; + + /** + * 销毁 worker 实例 + * 子实例需要销毁action + */ + public terminate(): void { + this.controller.terminate(); + // 设置终止标志位 + this.isTerminated = true; + } +} diff --git a/typescript/core/worker/worker.worker_thread.ts b/typescript/core/worker/worker.worker_thread.ts new file mode 100644 index 000000000..28e5cd64c --- /dev/null +++ b/typescript/core/worker/worker.worker_thread.ts @@ -0,0 +1,17 @@ +import { WorkerThreadController } from "./controller/worker.worker_thread.controller"; + +/** + * worker线程实例 + */ +export abstract class WorkerThreadWorker { + /** + * Worker 线程通信控制器 + */ + protected controller: WorkerThreadController; + + public constructor() { + this.controller = new WorkerThreadController(); + } + + protected abstract initActions(): void; +} From 4597695a6e0af6d049e0310226476c17b9828f58 Mon Sep 17 00:00:00 2001 From: iapyang Date: Tue, 28 Jun 2022 20:03:05 +0800 Subject: [PATCH 13/18] =?UTF-8?q?refactor:=20=E4=BF=AE=E6=94=B9=E4=B8=8Bex?= =?UTF-8?q?port?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- typescript/core/worker/action/worker.action.ts | 2 +- typescript/core/worker/controller/worker.controller.ts | 2 +- .../core/worker/controller/worker.main_thread.controller.ts | 2 +- .../core/worker/controller/worker.worker_thread.controller.ts | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/typescript/core/worker/action/worker.action.ts b/typescript/core/worker/action/worker.action.ts index 6a581fd9f..d588e6ab4 100644 --- a/typescript/core/worker/action/worker.action.ts +++ b/typescript/core/worker/action/worker.action.ts @@ -1,4 +1,4 @@ -import type WorkerBaseController from "../controller/worker.controller"; +import type { WorkerBaseController } from "../controller/worker.controller"; /** * 事务的基类 diff --git a/typescript/core/worker/controller/worker.controller.ts b/typescript/core/worker/controller/worker.controller.ts index 10c89f8c7..123bba98a 100644 --- a/typescript/core/worker/controller/worker.controller.ts +++ b/typescript/core/worker/controller/worker.controller.ts @@ -6,7 +6,7 @@ import { WorkerChannel } from '../worker.channel'; * * @class WorkerBaseController */ -export default class WorkerBaseController implements IWorkerController { +export class WorkerBaseController implements IWorkerController { /** * 原生 worker, 在子类中实例化 */ diff --git a/typescript/core/worker/controller/worker.main_thread.controller.ts b/typescript/core/worker/controller/worker.main_thread.controller.ts index 6cabc8380..bd7dcfc96 100644 --- a/typescript/core/worker/controller/worker.main_thread.controller.ts +++ b/typescript/core/worker/controller/worker.main_thread.controller.ts @@ -1,6 +1,6 @@ import { WorkerChannel } from "../worker.channel"; import type { IWorkerOptions } from "../worker.core"; -import WorkerBaseController from "./worker.controller"; +import { WorkerBaseController } from "./worker.controller"; export class WorkerMainThreadController extends WorkerBaseController { /** diff --git a/typescript/core/worker/controller/worker.worker_thread.controller.ts b/typescript/core/worker/controller/worker.worker_thread.controller.ts index 71abef337..2f4bac6cf 100644 --- a/typescript/core/worker/controller/worker.worker_thread.controller.ts +++ b/typescript/core/worker/controller/worker.worker_thread.controller.ts @@ -1,5 +1,5 @@ import { WorkerChannel } from "../worker.channel"; -import WorkerBaseController from "./worker.controller"; +import { WorkerBaseController } from "./worker.controller"; export class WorkerThreadController extends WorkerBaseController { public constructor() { From 77bfb877754d9d52f45e87d25d4f60032a1111d8 Mon Sep 17 00:00:00 2001 From: iapyang Date: Wed, 29 Jun 2022 14:00:05 +0800 Subject: [PATCH 14/18] =?UTF-8?q?feat:=20=E8=BE=93=E5=87=BAworkers?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- typescript/core/worker/workers.ts | 15 +++++++++++++++ typescript/index.ts | 3 +++ 2 files changed, 18 insertions(+) create mode 100644 typescript/core/worker/workers.ts diff --git a/typescript/core/worker/workers.ts b/typescript/core/worker/workers.ts new file mode 100644 index 000000000..55853f363 --- /dev/null +++ b/typescript/core/worker/workers.ts @@ -0,0 +1,15 @@ +import { WorkerChannel } from "./worker.channel"; +import { WorkerBaseController } from "./controller/worker.controller"; +import { WorkerMessageType } from "./worker.core"; +import { WorkerMainThreadController } from "./controller/worker.main_thread.controller"; +import { WorkerThreadController } from "./controller/worker.worker_thread.controller"; +import WorkerBaseAction from "./action/worker.action"; + +export const Workers = { + WorkerChannel, + WorkerBaseController, + WorkerMainThreadController, + WorkerThreadController, + WorkerBaseAction, + WorkerMessageType, +}; diff --git a/typescript/index.ts b/typescript/index.ts index fa9a08d19..c8be7c201 100644 --- a/typescript/index.ts +++ b/typescript/index.ts @@ -188,6 +188,7 @@ import { VerticalStickyLayout } from "./core/wrapper/layout/sticky/sticky.vertic import { HorizontalStickyLayout } from "./core/wrapper/layout/sticky/sticky.horizontal"; import { TableLayout } from "./core/wrapper/layout/layout.table"; import './shims-tsx'; +import { Workers } from "./core/worker/workers"; export interface BI extends _func, _i18n, _base, _inject, _var, _web, _utils { @@ -382,10 +383,12 @@ export interface BI extends _func, _i18n, _base, _inject, _var, _web, _utils { VerticalStickyLayout: typeof VerticalStickyLayout; HorizontalStickyLayout: typeof HorizontalStickyLayout; TableLayout: typeof TableLayout; + Workers: typeof Workers; } export default { Decorators: decorator, + Workers, }; export { OB, From a9188382a0bd15ac39d74b22949788dc1f1a40dc Mon Sep 17 00:00:00 2001 From: iapyang Date: Thu, 30 Jun 2022 11:37:38 +0800 Subject: [PATCH 15/18] =?UTF-8?q?fix:=20worker=E7=8E=AF=E5=A2=83=E7=A7=80?= =?UTF-8?q?=E8=B0=B7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/core/conflict.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/core/conflict.js b/src/core/conflict.js index e478c567d..dba9b8b6f 100644 --- a/src/core/conflict.js +++ b/src/core/conflict.js @@ -1,7 +1,7 @@ -if (!window.$ && !window.jQuery) { - window.jQuery = window.$ = BI.jQuery; +if (!_global.$ && !_global.jQuery) { + _global.jQuery = _global.$ = BI.jQuery; } -if (!window._) { - window._ = BI._; +if (!_global._) { + _global._ = BI._; } From 2548a525dd6b1ef4436744a880433688f71058d3 Mon Sep 17 00:00:00 2001 From: iapyang Date: Thu, 30 Jun 2022 14:02:47 +0800 Subject: [PATCH 16/18] =?UTF-8?q?refactor:=20=E8=B0=83=E6=95=B4=E4=B8=8B?= =?UTF-8?q?=E6=95=B0=E6=8D=AE=E7=BB=93=E6=9E=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- typescript/core/worker/action/worker.action.ts | 4 ++-- typescript/core/worker/worker.main_thread.ts | 9 +++++++++ typescript/core/worker/worker.worker_thread.ts | 11 +++++++++++ typescript/core/worker/workers.ts | 6 +++++- 4 files changed, 27 insertions(+), 3 deletions(-) diff --git a/typescript/core/worker/action/worker.action.ts b/typescript/core/worker/action/worker.action.ts index d588e6ab4..8f6920003 100644 --- a/typescript/core/worker/action/worker.action.ts +++ b/typescript/core/worker/action/worker.action.ts @@ -3,7 +3,7 @@ import type { WorkerBaseController } from "../controller/worker.controller"; /** * 事务的基类 */ -export default abstract class WorkerBaseAction { +export class WorkerBaseAction { /** * 通信控制器 */ @@ -24,5 +24,5 @@ export default abstract class WorkerBaseAction { /** * 添加事务的处理器 */ - protected abstract addActionHandler(): void; + protected addActionHandler() {} } diff --git a/typescript/core/worker/worker.main_thread.ts b/typescript/core/worker/worker.main_thread.ts index 3140bbbe7..42e0c18dd 100644 --- a/typescript/core/worker/worker.main_thread.ts +++ b/typescript/core/worker/worker.main_thread.ts @@ -1,3 +1,4 @@ +import type { WorkerBaseAction } from "./action/worker.action"; import { WorkerMainThreadController } from "./controller/worker.main_thread.controller"; import { IWorkerOptions } from "./worker.core"; @@ -37,4 +38,12 @@ export abstract class MainThreadWorker { // 设置终止标志位 this.isTerminated = true; } + + /** + * 实例化action + * @param Action action类 + */ + protected createAction(Action: T): InstanceType { + return (new Action(this.controller, this)) as InstanceType; + } } diff --git a/typescript/core/worker/worker.worker_thread.ts b/typescript/core/worker/worker.worker_thread.ts index 28e5cd64c..9907955fb 100644 --- a/typescript/core/worker/worker.worker_thread.ts +++ b/typescript/core/worker/worker.worker_thread.ts @@ -1,3 +1,4 @@ +import type { WorkerBaseAction } from "./action/worker.action"; import { WorkerThreadController } from "./controller/worker.worker_thread.controller"; /** @@ -11,7 +12,17 @@ export abstract class WorkerThreadWorker { public constructor() { this.controller = new WorkerThreadController(); + + this.initActions(); } protected abstract initActions(): void; + + /** + * 实例化action + * @param Action action类 + */ + protected createAction(Action: T): InstanceType { + return (new Action(this.controller, this)) as InstanceType; + } } diff --git a/typescript/core/worker/workers.ts b/typescript/core/worker/workers.ts index 55853f363..f02447120 100644 --- a/typescript/core/worker/workers.ts +++ b/typescript/core/worker/workers.ts @@ -3,7 +3,9 @@ import { WorkerBaseController } from "./controller/worker.controller"; import { WorkerMessageType } from "./worker.core"; import { WorkerMainThreadController } from "./controller/worker.main_thread.controller"; import { WorkerThreadController } from "./controller/worker.worker_thread.controller"; -import WorkerBaseAction from "./action/worker.action"; +import { WorkerBaseAction } from "./action/worker.action"; +import { MainThreadWorker } from "./worker.main_thread"; +import { WorkerThreadWorker } from "./worker.worker_thread"; export const Workers = { WorkerChannel, @@ -11,5 +13,7 @@ export const Workers = { WorkerMainThreadController, WorkerThreadController, WorkerBaseAction, + MainThreadWorker, + WorkerThreadWorker, WorkerMessageType, }; From 456f6669f8ef250f7951e86b7d2148fe9d530bf2 Mon Sep 17 00:00:00 2001 From: iapyang Date: Thu, 30 Jun 2022 16:00:34 +0800 Subject: [PATCH 17/18] =?UTF-8?q?chore:=20eslint=E4=BF=AE=E5=A4=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .eslintrc | 2 +- tsconfig.json | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/.eslintrc b/.eslintrc index c435e6b66..cb2d8acd2 100644 --- a/.eslintrc +++ b/.eslintrc @@ -26,7 +26,7 @@ }, "plugins": ["@typescript-eslint/eslint-plugin"], "overrides": [{ - "files": ["src/*.js","src/**/*.js", "demo/*.js", "demo/**/*.js", "i18n/**/*.js", "i18n/*.js", "test/**/*.js", "test/*.js"], + "files": ["src/*.js","src/**/*.js", "demo/*.js", "demo/**/*.js", "i18n/**/*.js", "i18n/*.js", "test/**/*.js", "test/*.js", "examples/*.js", "examples/**/*.js"], "extends": "plugin:@fui/es5", "rules": { "no-param-reassign": "off", diff --git a/tsconfig.json b/tsconfig.json index 8a4138e71..5630b275a 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -28,5 +28,7 @@ "types/*.d.ts", "src/*.js", "src/**/*.js", + "examples/*.js", + "examples/**/*.js", ] } \ No newline at end of file From e8665369785e828ae377b8d2c554e6da6565cadc Mon Sep 17 00:00:00 2001 From: iapyang Date: Thu, 30 Jun 2022 16:48:23 +0800 Subject: [PATCH 18/18] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0demo?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- examples/worker_new.html | 15 +++++ examples/worker_new/index.js | 105 ++++++++++++++++++++++++++++++++++ examples/worker_new/worker.js | 80 ++++++++++++++++++++++++++ 3 files changed, 200 insertions(+) create mode 100644 examples/worker_new.html create mode 100644 examples/worker_new/index.js create mode 100644 examples/worker_new/worker.js diff --git a/examples/worker_new.html b/examples/worker_new.html new file mode 100644 index 000000000..53e83c4ee --- /dev/null +++ b/examples/worker_new.html @@ -0,0 +1,15 @@ + + + + + + + Document + + + + +
+ + + \ No newline at end of file diff --git a/examples/worker_new/index.js b/examples/worker_new/index.js new file mode 100644 index 000000000..67646a1ec --- /dev/null +++ b/examples/worker_new/index.js @@ -0,0 +1,105 @@ +document.cookie = "test=demo"; + +// worker获取主线程资源 +var CookieAction = BI.inherit(BI.Workers.WorkerBaseAction, { + addActionHandler: function() { + this.controller.addActionHandler("Cookie", this.getCookie.bind(this)); + }, + + getCookie: function() { + return document.cookie; + } +}); + +// 调用worker计算 +var FibonacciAction = BI.inherit(BI.Workers.WorkerBaseAction, { + addActionHandler: function() {}, + + getResult: function(times) { + return this.controller.requestPromise("Fibonacci", { times: times }) + .then(function(v) { + console.log(v); + }); + } +}); + +// 主线程与worker多次交互 +const HeartBeatCheckAction = BI.inherit(BI.Workers.WorkerBaseAction, { + addActionHandler: function() { + this.controller.addActionHandler("HeartBeatChecked", this.recieveHeartBeatChecked.bind(this)); + }, + + recieveHeartBeatChecked: function (payload) { + console.log("heartbeat: " + payload.time); + }, + + startHeatBeatCheck: function() { + return this.controller.request("HeartBeatCheckStart"); + }, + + stopHeatBeatCheck: function() { + return this.controller.request("HeartBeatCheckStop"); + } +}); + +var WorkerThreadWorker = BI.inherit(BI.Workers.MainThreadWorker, { + initActions: function() { + this.cookieAction = this.createAction(CookieAction); + + this.fibonacciAction = this.createAction(FibonacciAction); + + this.heartBeatCheckAction = this.createAction(HeartBeatCheckAction); + }, + + calculateFibonacci: function(times) { + this.fibonacciAction.getResult(times); + }, + + startHeatBeatCheck: function() { + this.heartBeatCheckAction.startHeatBeatCheck(); + }, + + stopHeatBeatCheck: function() { + this.heartBeatCheckAction.stopHeatBeatCheck(); + } +}); + +var mainThreadWorker = new WorkerThreadWorker({ + workerUrl: "./worker_new/worker.js", + workerName: "demo" +}); + +BI.createWidget({ + type: "bi.vertical", + element: "#wrapper", + vgap: 10, + hgap: 10, + items: [ + { + type: "bi.button", + text: "点击计算斐波那契数列第40项", + width: 200, + handler: function() { + console.log("click"); + + mainThreadWorker.calculateFibonacci(40); + } + }, + { + type: "bi.button", + text: "开始心跳", + width: 200, + handler: function() { + mainThreadWorker.startHeatBeatCheck(); + } + }, + { + type: "bi.button", + text: "停止心跳", + width: 200, + handler: function() { + mainThreadWorker.stopHeatBeatCheck(); + } + } + ] +}); diff --git a/examples/worker_new/worker.js b/examples/worker_new/worker.js new file mode 100644 index 000000000..f30856b21 --- /dev/null +++ b/examples/worker_new/worker.js @@ -0,0 +1,80 @@ +self.importScripts("https://fanruan.design/fineui/fineui_without_jquery_polyfill.js"); + +var CookieAction = BI.inherit(BI.Workers.WorkerBaseAction, { + addActionHandler: function() {}, + + getCookie: function() { + return this.controller.requestPromise("Cookie"); + } +}); + +function fibonacci(n) { + if (n === 1 || n === 2) { + return 1; + } + + return fibonacci(n - 2) + fibonacci(n - 1); +} + +var FibonacciAction = BI.inherit(BI.Workers.WorkerBaseAction, { + addActionHandler: function() { + this.controller.addActionHandler("Fibonacci", this.getResult.bind(this)); + }, + + getResult: function(payload) { + return fibonacci(payload.times); + } +}); + +const HeartBeatCheckAction = BI.inherit(BI.Workers.WorkerBaseAction, { + addActionHandler: function() { + this.controller.addActionHandler("HeartBeatCheckStart", this.startHeatBeatCheck.bind(this)); + this.controller.addActionHandler("HeartBeatCheckStop", this.stopHeatBeatCheck.bind(this)); + }, + + startHeatBeatCheck: function() { + var self = this; + + if (!this.timer) { + console.log("heart beat check started"); + + this.timer = setInterval(function() { + // 模拟请求 + setTimeout(function() { + self.controller.request("HeartBeatChecked", { + time: new Date() + }); + }, 50); + }, 5 * 1000); + } else { + console.log("heart beat has already started!"); + } + }, + + stopHeatBeatCheck: function() { + console.log("heart beat check stopped"); + + clearInterval(this.timer); + } +}); + +var WorkerThreadWorker = BI.inherit(BI.Workers.WorkerThreadWorker, { + initActions: function() { + this.cookieAction = this.createAction(CookieAction); + + this.fibonacciAction = this.createAction(FibonacciAction); + + this.heartBeatCheckAction = this.createAction(HeartBeatCheckAction); + }, + + fetchCookie: function() { + return this.cookieAction.getCookie() + .then(function (v) { + console.log(v); + }); + } +}); + +var workerThreadWorker = new WorkerThreadWorker(); + +workerThreadWorker.fetchCookie();