Skip to content

Latest commit

 

History

History
458 lines (362 loc) · 13.4 KB

File metadata and controls

458 lines (362 loc) · 13.4 KB

FastEvent

WebSite

FastEvent 是一款设计精良,功能强大,类型安全,测试充分的事件触发器,提供了强大的事件订阅和发布机制,适用于nodejs/browser

安装

npm install fastevent
yarn add fastevent
pnpm add fastevent
bun add fastevent

指南

事件发布与订阅

FastEvent提供完整的事件发射与订阅功能,其 API 设计参考了eventemitter2

import { FastEvent } from 'fastevent';
const events = new FastEvent();

// 基本事件发布
const results = events.emit('user/login', { id: 1 });

// 异步事件发射
const results = await events.emitAsync('data/process', { items: [...] });

// 事件订阅
events.on('user/login', (message) => {
    console.log('用户登录:', message.payload);
});

// 一次性监听器
events.once('startup', () => console.log('应用已启动'));

// 带选项的监听
events.on('data/update', handler, {
    count: 3,       // 最大触发次数
    prepend: true,  // 添加到队列开头
    filter: (msg) => msg.payload.important // 只处理重要更新
});

// 全局监听器
events.onAny((message) => {
    console.log('事件发生:', message.type);
});

事件消息

监听器函数接收一个Message对象,该对象包含以下属性:

events.on('user/login', (message) => {
    // {
    //     type: 'user/login', // 事件名称
    //     payload: { id: 1 }, // 事件数据
    //     meta: {...}         // 事件元数据
    // }
});

保留事件

保留最后一次事件数据,后续订阅者可以在订阅时马上接收到事件值:

const events = new FastEvent();

// 发布并保留事件
events.emit('config/theme', { dark: true }, true);
// 等效于
events.emit('config/theme', { dark: true }, { retain: true });

// 后续订阅者立即收到保留的值
events.on('config/theme', (message) => {
    console.log('主题:', message.payload); // 立即输出: { dark: true }
});

层级事件发布

FastEvent支持发布与阅层级事件。

  • 默认事件层级分隔符为/,可以通过options.delimiter修改分隔符:
  • 在订阅事件时支持两种通配符,*匹配单层路径,**匹配多层路径(仅用于事件名称的末尾)
const events = new FastEvent();

// 匹配 user/*/login
events.on('user/*/login', (message) => {
    console.log('任何用户类型的登录:', message.payload);
});

// 匹配 user 下的所有事件
events.on('user/**', (message) => {
    console.log('所有用户相关事件:', message.payload);
});

// 触发事件
events.emit('user/admin/login', { id: 1 }); // 两个处理器都会被调用
events.emit('user/admin/profile/update', { name: 'New' }); // 只有 ** 处理器会被调用

移除监听器

FastEvent提供多种移除监听器的方式:

//
// 返回订阅器对象,通过它移除监听器,推荐使用这种方式
const subscriber = events.on('user/login', handler);
subscriber.off();

// 移除特定监听器
events.off(listener);
// 移除某个事件的所有监听器
events.off('user/login');
// 移除某个事件的特定监听器
events.off('user/login', listener);
// 使用通配符模式移除监听器
events.off('user/*');
// 移除所有监听器
events.offAll();
// 移除某个前缀下的所有监听器
events.offAll('user');

事件作用域

作用域允许你在特定的命名空间下处理事件。

注意,作用域与父事件发射器共享相同的监听器表:

const events = new FastEvent();

// 创建用户相关的作用域
const userScope = events.scope('user');

// 以下两种方式等效:
userScope.on('login', handler);
events.on('user/login', handler);

// 以下两种方式也等效:
userScope.emit('login', data);
events.emit('user/login', data);

// 清除作用域中的所有监听器
userScope.offAll(); // 等效于 events.offAll('user')

等待事件触发

使用waitFor等待特定事件发生,并支持超时。

const events = new FastEvent();

async function waitForLogin() {
    try {
        // 等待登录事件,超时时间 5 秒
        const userData = await events.waitFor('user/login', 5000);
        console.log('用户已登录:', userData);
    } catch (error) {
        console.log('登录等待超时');
    }
}

waitForLogin();
// 稍后触发登录事件
events.emit('user/login', { id: 1, name: 'Alice' });

事件钩子

FastEvent提供了多个钩子函数,用于在事件发射器生命周期的不同阶段进行操作。

const otherEvents = new FastEvent();
const events = new FastEvent({
    // 当添加新的监听器时调用
    onAddListener: (type, listener, options) => {
        console.log('添加了新的监听器:', type);
        // 返回 false 可以阻止监听器添加
        return false;
        // 可以直接返回一个FastEventSubscriber
        // 例如:将以 `@` 开头事件,则转移发到使用其他FastEvent
        if (type.startsWith('@')) {
            return otherEvents.on(type, listener, options);
        }
    },
    // 当移除监听器时调用
    onRemoveListener: (type, listener) => {
        console.log('移除了监听器:', type);
    },
    // 当清除监听器时调用
    onClearListeners: () => {
        console.log('已清除所有监听器');
    },
    // 当监听器抛出错误时调用
    onListenerError: (error, listener, message, args) => {
        console.error(`事件 ${message.type} 的监听器发生错误:`, error);
    },
    // 当监听器执行前调用
    onBeforeExecuteListener: (message, args) => {
        console.log('事件监听器前执行');
        // 返回 false 可以阻止监听器执行
        return false;

        // 将事件转发给其他FastEvent
        // 例如:将以 `@` 开头事件,则转发到使用其他FastEvent
        if (type.startsWith('@')) {
            return otherEvents.emit(message.type);
        }
    },
    // 当监听器执行后调用
    onAfterExecuteListener: (message, returns, listeners) => {
        console.log('事件监听器执行后');
        // 可以在在此拦截对返回值进行修改
    },
});

执行器

默认情况下,触发事件时会并且执行所有监听器。

FastEvent提供强大的监听器执行机制,允许开发者控制如何执行监听器。

import { race } from 'fastevent/executors';
const events = new FastEvent({
    executor: race(),
});

events.on('task/start', async () => {
    /* 耗时操作1 */
});
events.on('task/start', async () => {
    /* 耗时操作2 */
});

// 两个监听器会并行执行,返回最快的结果
await events.emitAsync('task/start');

// 也可以在事件发射器上单独设置执行器:
await events.emitAsync('task/start', 100, {
    executor: race(),
});

内置支持:

执行器 描述
parallel 默认,并发执行
race 并行执行器,使用 Promise.race 并行执行
balance 平均分配执行器
first 执行第一个监听器
last 执行最后一个监听器
random 随机选择监听器
series 串行执行器,依次执行监听器并返回最后一个结果
waterfall 依次执行监听器并返回最后一个结果,出错时中断
(listeners,message,args,execute)=>any[] 自定义执行器

监听管道

监听管道用于对在订阅事件时对监听函数进行包装,以实现各种常见的高级功能。

import { queue } from 'fastevent/pipes';
const events = new FastEvent();

//排队处理消息
events.on(
    'data/update',
    (data) => {
        console.log('处理数据:', data);
    },
    {
        pipes: [queue({ size: 10 })],
    },
);

内置支持:

管道 描述
queue 队列监听器,排队处理消息,支持优先级和超时控制
throttle 节流监听器
debounce 防抖监听器
timeout 超时监听器
retry 重试监听器,用于控制监听器执行失败后重试
memorize 缓存监听器,对监听器执行结果缓存

转发发布与订阅

FastEvent可以非常优雅的方式将发布和订阅转发到另外一个FastEvent实例。

const otherEmitter = new FastEvent();
const emitter = new FastEvent({
    onAddListener: (type, listener, options) => {
        // 订阅转发规则:当事件名称以`@/`开头时,将订阅转发到另外一个`FastEvent`实例
        if (type.startsWith('@/')) {
            return otherEmitter.on(type.substring(2), listener, options);
        }
    },
    onBeforeExecuteListener: (message, args) => {
        // 事件转发规则:当事件名称以`@/`开头时,就发布到其他`FastEvent`实例
        if (message.type.startsWith('@/')) {
            message.type = message.type.substring(2);
            return otherEmitter.emit(message, args);
        }
    },
});
const events: any[] = [];
otherEmitter.on('data', ({ payload }) => {
    events.push(payload);
});
// 订阅otherEmitter的data事件
emitter.on('@/data', ({ payload }) => {
    expect(payload).toBe(1);
    events.push(payload);
});
// 将data事件发布到otherEmitter
const subscriber = emitter.emit('@/data', 1);
subscriber.off();

元数据(Meta)

元数据是一种为事件提供额外上下文信息的机制。

你可以在不同层级设置元数据:全局、作用域级别或事件特定级别。

const events = new FastEvent({
    meta: {
        version: '1.0',
        environment: 'production',
    },
});

events.on('user/login', (message) => {
    console.log('事件数据:', message.payload);
    console.log('元数据:', message.meta); // 包含 type、version 和 environment
});

// 使用作用域级元数据
const userScope = events.scope('user', {
    meta: { domain: 'user' },
});
// 发布事件时添加特定元数据
userScope.emit(
    'login',
    { userId: '123' },
    {
        meta: { timestamp: Date.now() }, // 事件特定元数据
    },
);

// 监听器接收合并后的元数据
userScope.on('login', (message) => {
    console.log('元数据:', message.meta);
});

事件类型定义

FastEvent具备完整的TypeScript类型支持。

// 定义具有不同载荷类型的事件
interface ComplexEvents {
    'data/number': number;
    'data/string': string;
    'data/object': { value: any };
}

const events = new FastEvent<ComplexEvents>();

// TypeScript 确保每个事件的类型安全
events.on('data/number', (message) => {
    const sum = message.payload + 1; // payload 的类型为 number
});

// 所有的事件发送都会进行类型检查
events.emit('data/number', 42);
events.emit('data/string', 'hello');
events.emit('data/object', { value: true });

消息转换

默认情况下,FastEvent监听器接收到的消息格式统一为FastEventMessage,包括typepayload和可选的meta三个字段。 但是,FastEvent也提供了自定义能力,让每个事件的接收到的消息均不一样,并且具有相应的类型提示。

import { FastEvent, FastEventOptions, NotPayload } from 'fastevent';

// {<事件名称>:<消息有效负载payload类型>}
type CustomEvents = {
    // NotPayload用来标识不是一个payload,而是完整的消息体
    click: NotPayload<{ x: number; y: number }>;
    'div/mousemove': boolean;
    'div/scroll': number;
    'div/focus': string;
};

const emitter = new FastEvent<CustomEvents>({
    // 将标准的FastEventMessage转换为你需要的格式
    transform: (message: FastEventMessage) => {
        if (['div/click', 'div/mousemove'].includes(message.type)) {
            return message.payload;
        }
        return message;
    },
});

emitter.on('click', (message) => {
    // typeof message === { x: number; y: number }  ✅
});

NotPayload只用于标识部分事件,也可以使用TransformedEvents来将所有事件消息。

import { FastEvent, FastEventOptions, TransformedEvents } from 'fastevent';

// {<事件名称>:<消息类型>}
type CustomEvents = TransformedEvents<{
    click: { x: number; y: number };
    'div/mousemove': boolean;
    'div/scroll': number;
    'div/focus': string;
}>;
  • transform用于将标准的 FastEventMessage 转换为你需要的格式
  • NotPayloadTransformedEvents用于声明类型,以便在on/once时为监听器提供类型声明。

单元测试

FastEvent经过充分的单元测试,累计单元测试用例超过320+,测试覆盖率99%+

License

MIT

更多详细的文档见WebSite