Skip to content

Commit f8451eb

Browse files
authored
feat: support stop event emit (#807)
1 parent ac9d577 commit f8451eb

File tree

4 files changed

+171
-3
lines changed

4 files changed

+171
-3
lines changed

src/common/event/__tests__/eventEmitter.test.ts

+38
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { EventEmitter } from '../index';
2+
import type { ListenerEventContext } from '../index';
23

34
describe('Test the EventEmitter class', () => {
45
const event = new EventEmitter();
@@ -60,4 +61,41 @@ describe('Test the EventEmitter class', () => {
6061
evt.unsubscribe(eventName);
6162
expect(evt.count(eventName)).toBe(0);
6263
});
64+
65+
test('Should stop delivering event', () => {
66+
const evt = new EventEmitter();
67+
const eventName = 'event1';
68+
69+
const mockFn = jest.fn();
70+
evt.subscribe(eventName, mockFn);
71+
evt.subscribe(eventName, function (this: ListenerEventContext) {
72+
this.stopDelivery();
73+
mockFn();
74+
});
75+
evt.subscribe(eventName, mockFn);
76+
77+
evt.emit(eventName);
78+
expect(mockFn).toBeCalledTimes(2);
79+
});
80+
81+
test('Should stop async function in event chain', () => {
82+
const evt = new EventEmitter();
83+
const eventName = 'event1';
84+
85+
const mockFn = jest.fn();
86+
evt.subscribe(eventName, mockFn);
87+
evt.subscribe(eventName, async function (this: ListenerEventContext) {
88+
await new Promise<void>((resolve) => {
89+
setTimeout(() => {
90+
this.stopDelivery();
91+
resolve();
92+
}, 0);
93+
});
94+
mockFn();
95+
});
96+
evt.subscribe(eventName, mockFn);
97+
98+
evt.emit(eventName);
99+
expect(mockFn).toBeCalledTimes(2);
100+
});
63101
});

src/common/event/eventEmitter.ts

+17-3
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
export interface ListenerEventContext {
2+
stopDelivery: () => void;
3+
}
4+
15
export class EventEmitter {
26
private _events = new Map<string, Function[]>();
37

@@ -9,9 +13,19 @@ export class EventEmitter {
913
public emit(name: string, ...args) {
1014
const events = this._events.get(name);
1115
if (events && events.length > 0) {
12-
events.forEach((callEvent) => {
13-
callEvent(...args);
14-
});
16+
let continued = true;
17+
// call in descending order
18+
for (let index = events.length - 1; index >= 0; index--) {
19+
if (continued) {
20+
const evt = events[index];
21+
evt.call(
22+
{
23+
stopDelivery: () => (continued = false),
24+
},
25+
...args
26+
);
27+
}
28+
}
1529
}
1630
}
1731

website/docs/advanced/event.md

+59
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
---
2+
title: Event
3+
sidebar_label: Event
4+
---
5+
6+
An event actually is a callback function. The event-emitter calls the function when the corresponding event be emitted. For example, there is a button be called at editor, and it will emit an event called `molecule.editor.onClick`. And event-emitter will find the corresponding event according to the `molecule.editor.onClick` and then call it.
7+
8+
If there are multiples functions related to an event, these functions will be called in chain. And this is called **event chains**.
9+
10+
## Example
11+
12+
At most situations, we don't have to care about the event chains. Every functions whatever built-in or user-defined are all have to be called.
13+
14+
But if you want to stop the chain in some situations, you definitely have this situation, you could call `this.stopDelivery` to stop the delivery of event chains.
15+
16+
:::tips
17+
The `this` context of the function is filled-in by Molecule.
18+
:::
19+
20+
For example, if we want to stop delivery after calculating, and we can do like this:
21+
22+
```ts Stop delivery in synchronous
23+
molecule.editor.onClick(() => {
24+
// if result is true, and this function won't be called
25+
console.log('onClick');
26+
});
27+
28+
molecule.editor.onClick(function (this: ListenerEventContext) {
29+
// do something
30+
const result = calculated();
31+
if (result) {
32+
this.stopDelivery();
33+
}
34+
});
35+
```
36+
37+
```ts Stop delivery in asynchronous
38+
molecule.editor.onClick(() => {
39+
// if result is true, and this function won't be called
40+
console.log('onClick');
41+
});
42+
43+
molecule.editor.onClick(async function (this: ListenerEventContext) {
44+
await new Promise<void>((resolve) => {
45+
setTimeout(() => {
46+
this.stopDelivery();
47+
resolve();
48+
}, 0);
49+
});
50+
});
51+
```
52+
53+
## Order
54+
55+
Functions in event chains are First In, Last Called(FILC). So the built-in functions are involved first, and called last.
56+
57+
Oppositely the user-defined functions are involved last, but called first.
58+
59+
So user-defined functions are ability to stop the delivery to the built-in functions.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
---
2+
title: 订阅事件
3+
sidebar_label: 订阅事件
4+
---
5+
6+
一个订阅事件本质上就是一个回调函数。事件触发器将会在对应事件被触发的时候执行对应的订阅事件。例如:在 editor 上有一个按钮,点击按钮将会触发 `molecule.editor.onClick` 该事件。那么事件触发器将会根据 `molecule.editor.onClick` 找到对应的事件,并执行它。
7+
8+
如果某个事件有被多处订阅,那么这个事件将会有很多回调函数。这些函数如果被触发将会被链式调用。我们称之为事件链。
9+
10+
## 示例
11+
12+
在大部分情况下,我们并不需要关心事件链的存在。每一个回调函数都有其存在的理由,不论是内置还是用户定义的函数。
13+
14+
然而如果在某些业务场景下,我们需要阻止事件链的传递(你必然会遇到这样子的场景),那么我们只需要调用 `this.stopDelivery` 来阻止事件链的传递即可。
15+
16+
:::tips
17+
回调函数的(`this`)上下文会被 Molecule 修改。
18+
:::
19+
20+
例如,如果我们想在计算获得某种结果后,阻止事件的传递,那么我们可以这样操作:
21+
22+
```ts 同步阻止事件传递
23+
molecule.editor.onClick(() => {
24+
// if result is true, and this function won't be called
25+
console.log('onClick');
26+
});
27+
28+
molecule.editor.onClick(function (this: ListenerEventContext) {
29+
// do something
30+
const result = calculated();
31+
if (result) {
32+
this.stopDelivery();
33+
}
34+
});
35+
```
36+
37+
```ts 异步组织事件传递
38+
molecule.editor.onClick(() => {
39+
// if result is true, and this function won't be called
40+
console.log('onClick');
41+
});
42+
43+
molecule.editor.onClick(async function (this: ListenerEventContext) {
44+
await new Promise<void>((resolve) => {
45+
setTimeout(() => {
46+
this.stopDelivery();
47+
resolve();
48+
}, 0);
49+
});
50+
});
51+
```
52+
53+
## 顺序
54+
55+
事件链中的函数遵循先进后执行(FILC)。所以内置回调函数是先进后执行
56+
57+
相对的,用户定义的回调函数是后进先执行。所以用户定义的回调函数可以阻止事件传递到内置回调函数。

0 commit comments

Comments
 (0)