From 5c6a3455b039383dc78eaf1f08fb3ac7d53c379d Mon Sep 17 00:00:00 2001 From: roymondchen Date: Wed, 6 Dec 2023 15:28:18 +0800 Subject: [PATCH] =?UTF-8?q?feat(editor):=20service=E6=89=A9=E5=B1=95?= =?UTF-8?q?=E6=94=AF=E6=8C=81=E8=AE=BE=E7=BD=AE=E6=88=90=E5=90=8C=E6=AD=A5?= =?UTF-8?q?=E7=9A=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../layouts/workspace/viewer/ViewerMenu.vue | 4 +- packages/editor/src/services/BaseService.ts | 89 +++++++++++++------ packages/editor/src/services/codeBlock.ts | 8 +- packages/editor/src/services/editor.ts | 48 +++++----- packages/editor/src/services/props.ts | 16 ++-- packages/editor/src/services/storage.ts | 36 +++++--- packages/editor/src/services/ui.ts | 5 +- packages/editor/src/utils/compose.ts | 31 +++++-- .../editor/tests/unit/services/editor.spec.ts | 2 +- 9 files changed, 157 insertions(+), 82 deletions(-) diff --git a/packages/editor/src/layouts/workspace/viewer/ViewerMenu.vue b/packages/editor/src/layouts/workspace/viewer/ViewerMenu.vue index 3dea1a981..e72919d97 100644 --- a/packages/editor/src/layouts/workspace/viewer/ViewerMenu.vue +++ b/packages/editor/src/layouts/workspace/viewer/ViewerMenu.vue @@ -127,9 +127,9 @@ watch( { immediate: true }, ); -const show = async (e: MouseEvent) => { +const show = (e: MouseEvent) => { menu.value?.show(e); - const data = await storageService.getItem(COPY_STORAGE_KEY); + const data = storageService.getItem(COPY_STORAGE_KEY); canPaste.value = data !== 'undefined' && !!data; }; diff --git a/packages/editor/src/services/BaseService.ts b/packages/editor/src/services/BaseService.ts index d7ede9f16..22f8a51e3 100644 --- a/packages/editor/src/services/BaseService.ts +++ b/packages/editor/src/services/BaseService.ts @@ -24,13 +24,48 @@ const methodName = (prefix: string, name: string) => `${prefix}${name[0].toUpper const isError = (error: any): boolean => Object.prototype.toString.call(error) === '[object Error]'; -const doAction = async ( +const doAction = ( args: any[], scope: any, sourceMethod: any, beforeMethodName: string, afterMethodName: string, - fn: (args: any[], next?: Function | undefined) => Promise, + fn: (args: any[], next?: Function | undefined) => void, +) => { + try { + let beforeArgs = args; + + for (const beforeMethod of scope.pluginOptionsList[beforeMethodName]) { + beforeArgs = beforeMethod(...beforeArgs) || []; + + if (isError(beforeArgs)) throw beforeArgs; + + if (!Array.isArray(beforeArgs)) { + beforeArgs = [beforeArgs]; + } + } + + let returnValue: any = fn(beforeArgs, sourceMethod.bind(scope)); + + for (const afterMethod of scope.pluginOptionsList[afterMethodName]) { + returnValue = afterMethod(returnValue, ...beforeArgs); + + if (isError(returnValue)) throw returnValue; + } + + return returnValue; + } catch (error) { + throw error; + } +}; + +const doAsyncAction = async ( + args: any[], + scope: any, + sourceMethod: any, + beforeMethodName: string, + afterMethodName: string, + fn: (args: any[], next?: Function | undefined) => Promise | void, ) => { try { let beforeArgs = args; @@ -112,10 +147,10 @@ export default class extends EventEmitter { private taskList: (() => Promise)[] = []; private doingTask = false; - constructor(methods: string[] = [], serialMethods: string[] = []) { + constructor(methods: { name: string; isAsync: boolean }[] = [], serialMethods: string[] = []) { super(); - methods.forEach((propertyName: string) => { + methods.forEach(({ name: propertyName, isAsync }) => { const scope = this as any; const sourceMethod = scope[propertyName]; @@ -127,32 +162,34 @@ export default class extends EventEmitter { this.pluginOptionsList[afterMethodName] = []; this.middleware[propertyName] = []; - const fn = compose(this.middleware[propertyName]); + const fn = compose(this.middleware[propertyName], isAsync); Object.defineProperty(scope, propertyName, { - value: async (...args: any[]) => { - if (!serialMethods.includes(propertyName)) { - return doAction(args, scope, sourceMethod, beforeMethodName, afterMethodName, fn); - } - - // 由于async await,所以会出现函数执行到await时让出线程,导致执行顺序出错,例如调用了select(1) -> update -> select(2),这个时候就有可能出现update了2; - // 这里保证函数调用严格按顺序执行; - const promise = new Promise((resolve, reject) => { - this.taskList.push(async () => { - try { - const value = await doAction(args, scope, sourceMethod, beforeMethodName, afterMethodName, fn); - resolve(value); - } catch (e) { - reject(e); + value: isAsync + ? async (...args: any[]) => { + if (!serialMethods.includes(propertyName)) { + return doAsyncAction(args, scope, sourceMethod, beforeMethodName, afterMethodName, fn); } - }); - }); - if (!this.doingTask) { - this.doTask(); - } + // 由于async await,所以会出现函数执行到await时让出线程,导致执行顺序出错,例如调用了select(1) -> update -> select(2),这个时候就有可能出现update了2; + // 这里保证函数调用严格按顺序执行; + const promise = new Promise((resolve, reject) => { + this.taskList.push(async () => { + try { + const value = await doAsyncAction(args, scope, sourceMethod, beforeMethodName, afterMethodName, fn); + resolve(value); + } catch (e) { + reject(e); + } + }); + }); + + if (!this.doingTask) { + this.doTask(); + } - return promise; - }, + return promise; + } + : (...args: any[]) => doAction(args, scope, sourceMethod, beforeMethodName, afterMethodName, fn), }); }); } diff --git a/packages/editor/src/services/codeBlock.ts b/packages/editor/src/services/codeBlock.ts index 92b625f00..dc1526585 100644 --- a/packages/editor/src/services/codeBlock.ts +++ b/packages/editor/src/services/codeBlock.ts @@ -38,7 +38,13 @@ class CodeBlock extends BaseService { }); constructor() { - super(['setCodeDslById', 'setEditStatus', 'setCombineIds', 'setUndeleteableList', 'deleteCodeDslByIds']); + super([ + { name: 'setCodeDslById', isAsync: true }, + { name: 'setEditStatus', isAsync: true }, + { name: 'setCombineIds', isAsync: true }, + { name: 'setUndeleteableList', isAsync: true }, + { name: 'deleteCodeDslByIds', isAsync: true }, + ]); } /** diff --git a/packages/editor/src/services/editor.ts b/packages/editor/src/services/editor.ts index 4a7f25f90..5f508fdfd 100644 --- a/packages/editor/src/services/editor.ts +++ b/packages/editor/src/services/editor.ts @@ -61,27 +61,27 @@ class Editor extends BaseService { constructor() { super( [ - 'getLayout', - 'select', - 'doAdd', - 'add', - 'doRemove', - 'remove', - 'doUpdate', - 'update', - 'sort', - 'copy', - 'paste', - 'doPaste', - 'doAlignCenter', - 'alignCenter', - 'moveLayer', - 'moveToContainer', - 'move', - 'undo', - 'redo', - 'highlight', - 'dragTo', + { name: 'getLayout', isAsync: true }, + { name: 'select', isAsync: true }, + { name: 'doAdd', isAsync: true }, + { name: 'add', isAsync: true }, + { name: 'doRemove', isAsync: true }, + { name: 'remove', isAsync: true }, + { name: 'doUpdate', isAsync: true }, + { name: 'update', isAsync: true }, + { name: 'sort', isAsync: true }, + { name: 'copy', isAsync: true }, + { name: 'paste', isAsync: true }, + { name: 'doPaste', isAsync: true }, + { name: 'doAlignCenter', isAsync: true }, + { name: 'alignCenter', isAsync: true }, + { name: 'moveLayer', isAsync: true }, + { name: 'moveToContainer', isAsync: true }, + { name: 'move', isAsync: true }, + { name: 'undo', isAsync: true }, + { name: 'redo', isAsync: true }, + { name: 'highlight', isAsync: true }, + { name: 'dragTo', isAsync: true }, ], // 需要注意循环依赖问题,如果函数间有相互调用的话,不能设置为串行调用 ['select', 'update', 'moveLayer'], @@ -597,8 +597,8 @@ class Editor extends BaseService { * @param config 组件节点配置 * @returns 组件节点配置 */ - public async copy(config: MNode | MNode[]): Promise { - await storageService.setItem(COPY_STORAGE_KEY, Array.isArray(config) ? config : [config], { + public copy(config: MNode | MNode[]): void { + storageService.setItem(COPY_STORAGE_KEY, Array.isArray(config) ? config : [config], { protocol: Protocol.OBJECT, }); } @@ -609,7 +609,7 @@ class Editor extends BaseService { * @returns 添加后的组件节点配置 */ public async paste(position: PastePosition = {}): Promise { - const config: MNode[] = await storageService.getItem(COPY_STORAGE_KEY); + const config: MNode[] = storageService.getItem(COPY_STORAGE_KEY); if (!Array.isArray(config)) return; diff --git a/packages/editor/src/services/props.ts b/packages/editor/src/services/props.ts index 19978427b..87d8d8963 100644 --- a/packages/editor/src/services/props.ts +++ b/packages/editor/src/services/props.ts @@ -36,14 +36,14 @@ class Props extends BaseService { constructor() { super([ - 'setPropsConfig', - 'getPropsConfig', - 'setPropsValue', - 'getPropsValue', - 'createId', - 'setNewItemId', - 'fillConfig', - 'getDefaultPropsValue', + { name: 'setPropsConfig', isAsync: true }, + { name: 'getPropsConfig', isAsync: true }, + { name: 'setPropsValue', isAsync: true }, + { name: 'getPropsValue', isAsync: true }, + { name: 'createId', isAsync: false }, + { name: 'setNewItemId', isAsync: true }, + { name: 'fillConfig', isAsync: true }, + { name: 'getDefaultPropsValue', isAsync: true }, ]); } diff --git a/packages/editor/src/services/storage.ts b/packages/editor/src/services/storage.ts index 1fecb1a3a..be04e3bb5 100644 --- a/packages/editor/src/services/storage.ts +++ b/packages/editor/src/services/storage.ts @@ -25,7 +25,14 @@ export class WebStorage extends BaseService { private namespace = 'tmagic'; constructor() { - super(['getStorage', 'getNamespace', 'clear', 'getItem', 'removeItem', 'setItem']); + super([ + { name: 'getStorage', isAsync: false }, + { name: 'getNamespace', isAsync: false }, + { name: 'clear', isAsync: false }, + { name: 'getItem', isAsync: false }, + { name: 'removeItem', isAsync: false }, + { name: 'setItem', isAsync: false }, + ]); } /** @@ -38,26 +45,27 @@ export class WebStorage extends BaseService { * }, * }); */ - public async getStorage(): Promise { + public getStorage(): Storage { return this.storage; } - public async getNamespace(): Promise { + public getNamespace(): string { return this.namespace; } /** * 清理,支持storageService.usePlugin */ - public async clear(): Promise { - const storage = await this.getStorage(); + public clear(): void { + const storage = this.getStorage(); storage.clear(); } /** * 获取存储项,支持storageService.usePlugin */ - public async getItem(key: string, options: Options = {}): Promise { - const [storage, namespace] = await Promise.all([this.getStorage(), this.getNamespace()]); + public getItem(key: string, options: Options = {}): any { + const storage = this.getStorage(); + const namespace = this.getNamespace(); const { protocol = options.protocol, item } = this.getValueAndProtocol( storage.getItem(`${options.namespace || namespace}:${key}`), ); @@ -80,24 +88,26 @@ export class WebStorage extends BaseService { /** * 获取指定索引位置的key */ - public async key(index: number): Promise { - const storage = await this.getStorage(); + public key(index: number): string | null { + const storage = this.getStorage(); return storage.key(index); } /** * 移除存储项,支持storageService.usePlugin */ - public async removeItem(key: string, options: Options = {}): Promise { - const [storage, namespace] = await Promise.all([this.getStorage(), this.getNamespace()]); + public removeItem(key: string, options: Options = {}): void { + const storage = this.getStorage(); + const namespace = this.getNamespace(); storage.removeItem(`${options.namespace || namespace}:${key}`); } /** * 设置存储项,支持storageService.usePlugin */ - public async setItem(key: string, value: any, options: Options = {}): Promise { - const [storage, namespace] = await Promise.all([this.getStorage(), this.getNamespace()]); + public setItem(key: string, value: any, options: Options = {}): void { + const storage = this.getStorage(); + const namespace = this.getNamespace(); let item = value; const protocol = options.protocol ? `${options.protocol}:` : ''; if (typeof value === Protocol.STRING || typeof value === Protocol.NUMBER) { diff --git a/packages/editor/src/services/ui.ts b/packages/editor/src/services/ui.ts index 08233fa13..e6b53ba79 100644 --- a/packages/editor/src/services/ui.ts +++ b/packages/editor/src/services/ui.ts @@ -52,7 +52,10 @@ const state = reactive({ class Ui extends BaseService { constructor() { - super(['zoom', 'calcZoom']); + super([ + { name: 'zoom', isAsync: true }, + { name: 'calcZoom', isAsync: true }, + ]); } public set(name: K, value: T) { diff --git a/packages/editor/src/utils/compose.ts b/packages/editor/src/utils/compose.ts index 7e2e5e49f..0748a6b15 100644 --- a/packages/editor/src/utils/compose.ts +++ b/packages/editor/src/utils/compose.ts @@ -2,7 +2,7 @@ * @param {Array} middleware * @return {Function} */ -export const compose = (middleware: Function[]) => { +export const compose = (middleware: Function[], isAsync: boolean) => { if (!Array.isArray(middleware)) throw new TypeError('Middleware 必须是一个数组!'); for (const fn of middleware) { if (typeof fn !== 'function') throw new TypeError('Middleware 必须由函数组成!'); @@ -17,16 +17,35 @@ export const compose = (middleware: Function[]) => { // last called middleware # let index = -1; return dispatch(0); - function dispatch(i: number): Promise { - if (i <= index) return Promise.reject(new Error('next() 被多次调用')); + function dispatch(i: number): Promise | void { + if (i <= index) { + const error = new Error('next() 被多次调用'); + if (isAsync) { + return Promise.reject(error); + } + throw error; + } index = i; let fn = middleware[i]; if (i === middleware.length && next) fn = next; - if (!fn) return Promise.resolve(); + if (!fn) { + if (isAsync) { + return Promise.resolve(); + } + return; + } + + if (isAsync) { + try { + return Promise.resolve(fn(...args, dispatch.bind(null, i + 1))); + } catch (err) { + return Promise.reject(err); + } + } try { - return Promise.resolve(fn(...args, dispatch.bind(null, i + 1))); + return fn(...args, dispatch.bind(null, i + 1)); } catch (err) { - return Promise.reject(err); + throw err; } } }; diff --git a/packages/editor/tests/unit/services/editor.spec.ts b/packages/editor/tests/unit/services/editor.spec.ts index 367fc69c8..659b3f6b5 100644 --- a/packages/editor/tests/unit/services/editor.spec.ts +++ b/packages/editor/tests/unit/services/editor.spec.ts @@ -365,7 +365,7 @@ describe('copy', () => { test('正常', async () => { const node = editorService.getNodeById(NodeId.NODE_ID2); await editorService.copy(node!); - const str = await storageService.getItem(COPY_STORAGE_KEY); + const str = storageService.getItem(COPY_STORAGE_KEY); expect(str).toHaveLength(1); }); });