0

基于 IEventE

 2 years ago
source link: https://blog.rxliuli.com/p/53784162ade2454498a210a59796fae6/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
基于 IEventEmitter 封装更上层的 api

基于 IEvent_

2021年10月24日 凌晨

2.9k 字

9 分钟

本文最后更新于:2021年10月24日 晚上

已经有许许多多类似的封装了,例如最流行的可能是 ChromeLab 的 comlink

之前虽然通过 IEventEmitter 声明了子应用与系统之间的通信,但实际使用时可能不够简单,所以基于它封装更上层的 api MessageChannel,希望它能够有以下功能。

  • 在一边通过 on 方法注册后应该可以直接返回值并自动发回至调用者
  • 默认对参数及返回值做一些处理以提高性能,主要是通过 JSON.stringify 实现(待完成测试)
import { IEventEmitter } from "./IEventEmitter";
// 这只是一个简单的 id 生成器
import { CallbackIdGenerator } from "./CallbackIdGenerator";

export class MessageChannel {
  constructor(private readonly emitter: IEventEmitter) {}

  invoke(channel: string, data?: any): Promise<any> {
    return new Promise((resolve) => {
      const callbackId = CallbackIdGenerator.generate();
      this.emitter.on(callbackId, (msg) => {
        resolve(msg === undefined ? undefined : JSON.parse(msg));
        this.emitter.offByChannel(callbackId);
      });
      this.emitter.emit(channel, JSON.stringify({ data, callbackId }));
    });
  }

  on(channel: string, handle: (data: any) => any) {
    this.emitter.on(channel, async (msg) => {
      const value = JSON.parse(msg) as { callbackId: string; data: any };
      const res = await handle(value.data);
      if (value.callbackId) {
        this.emitter.emit(value.callbackId, JSON.stringify(res));
      }
    });
  }

  offByChannel(channel: string) {
    this.emitter.offByChannel(channel);
  }
}
messageChannel.on("hello", (name: string) => `hello ${name}`);
expect(await messageChannel.invoke("hello", "liuli")).toBe("hello liuli");

但它仍存在一些问题

  • invoke 方法一定会包含回调,即便是事实上不需要关心回调的也一样

支持仅触发事件的 emit 函数

主要思路是添加 type 字段标识调用的类型,即由调用者决定是否需要关心返回值。

import { IEventEmitter } from "./IEventEmitter";
import { CallbackIdGenerator } from "./CallbackIdGenerator";

type MessageChannelData = { data?: any } & (
  | { type: "emit" }
  | { type: "invoke"; callbackId: string }
);

export class MessageChannel {
  constructor(private readonly emitter: IEventEmitter) {}

  invoke(channel: string, data?: any): Promise<any> {
    return new Promise((resolve) => {
      const callbackId = CallbackIdGenerator.generate();
      this.emitter.on(callbackId, (msg) => {
        resolve(msg === undefined ? undefined : JSON.parse(msg));
        this.emitter.offByChannel(callbackId);
      });
      this.emitter.emit(
        channel,
        JSON.stringify({
          type: "invoke",
          data,
          callbackId,
        } as MessageChannelData)
      );
    });
  }

  emit(channel: string, data?: any): void {
    this.emitter.emit(channel, {
      type: "emit",
      data,
    } as MessageChannelData);
  }

  on(channel: string, handle: (data: any) => any) {
    this.emitter.on(channel, async (msg) => {
      const value = JSON.parse(msg) as MessageChannelData;
      const res = await handle(value.data);
      if (value.type === "invoke") {
        this.emitter.emit(value.callbackId, JSON.stringify(res));
      }
    });
  }

  offByChannel(channel: string) {
    this.emitter.offByChannel(channel);
  }
}

最终希望达成什么样子?

下面是一些希望能够做到的使用方式

interface ITestApi {
  hello(name: string): string;
}

const messageChannel = new MessageChannel(new EventEmitter());
messageChannel.expose("test", {
  hello(name: string) {
    return `hello ${name}`;
  },
});
const client = messageChannel.wrap<ITestApi>("test");
console.log(await client.hello("liuli"));

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK