@idlebox/common 1.5.20 → 1.5.22

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (95) hide show
  1. package/lib/{autoindex.d.ts → autoindex.generated.d.ts} +5 -4
  2. package/lib/autoindex.generated.d.ts.map +1 -0
  3. package/lib/{autoindex.js → autoindex.generated.js} +12 -6
  4. package/lib/autoindex.generated.js.map +1 -0
  5. package/lib/debugging/inspect.d.ts.map +1 -1
  6. package/lib/debugging/inspect.js +2 -1
  7. package/lib/debugging/inspect.js.map +1 -1
  8. package/lib/error/convert-unknown.d.ts.map +1 -1
  9. package/lib/error/convert-unknown.js +4 -0
  10. package/lib/error/convert-unknown.js.map +1 -1
  11. package/lib/error/pretty.nodejs.d.ts.map +1 -1
  12. package/lib/error/pretty.nodejs.js +4 -1
  13. package/lib/error/pretty.nodejs.js.map +1 -1
  14. package/lib/error/stack-parser.v8.js +2 -2
  15. package/lib/error/stack-parser.v8.js.map +1 -1
  16. package/lib/lifecycle/dispose/async-disposable.d.ts +11 -2
  17. package/lib/lifecycle/dispose/async-disposable.d.ts.map +1 -1
  18. package/lib/lifecycle/dispose/async-disposable.js +26 -6
  19. package/lib/lifecycle/dispose/async-disposable.js.map +1 -1
  20. package/lib/lifecycle/dispose/bridges/native.d.ts +2 -2
  21. package/lib/lifecycle/dispose/bridges/native.d.ts.map +1 -1
  22. package/lib/lifecycle/dispose/bridges/native.js +5 -5
  23. package/lib/lifecycle/dispose/bridges/native.js.map +1 -1
  24. package/lib/lifecycle/dispose/bridges/streams.d.ts +12 -0
  25. package/lib/lifecycle/dispose/bridges/streams.d.ts.map +1 -1
  26. package/lib/lifecycle/dispose/bridges/streams.js +30 -30
  27. package/lib/lifecycle/dispose/bridges/streams.js.map +1 -1
  28. package/lib/lifecycle/dispose/disposable.d.ts +11 -8
  29. package/lib/lifecycle/dispose/disposable.d.ts.map +1 -1
  30. package/lib/lifecycle/dispose/disposable.js +65 -27
  31. package/lib/lifecycle/dispose/disposable.js.map +1 -1
  32. package/lib/lifecycle/dispose/sync-disposable.d.ts +6 -1
  33. package/lib/lifecycle/dispose/sync-disposable.d.ts.map +1 -1
  34. package/lib/lifecycle/dispose/sync-disposable.js +21 -5
  35. package/lib/lifecycle/dispose/sync-disposable.js.map +1 -1
  36. package/lib/lifecycle/event/event.d.ts +27 -4
  37. package/lib/lifecycle/event/event.d.ts.map +1 -1
  38. package/lib/lifecycle/event/event.js +86 -37
  39. package/lib/lifecycle/event/event.js.map +1 -1
  40. package/lib/lifecycle/event/type.d.ts +2 -2
  41. package/lib/lifecycle/event/type.d.ts.map +1 -1
  42. package/lib/map-and-set/required-map.d.ts.map +1 -1
  43. package/lib/map-and-set/required-map.js +8 -0
  44. package/lib/map-and-set/required-map.js.map +1 -1
  45. package/lib/promise/deferred-promise.d.ts +1 -0
  46. package/lib/promise/deferred-promise.d.ts.map +1 -1
  47. package/lib/promise/deferred-promise.js +5 -5
  48. package/lib/promise/deferred-promise.js.map +1 -1
  49. package/lib/promise/is-promise.d.ts +2 -0
  50. package/lib/promise/is-promise.d.ts.map +1 -0
  51. package/lib/promise/is-promise.js +4 -0
  52. package/lib/promise/is-promise.js.map +1 -0
  53. package/lib/re-export.d.ts.map +1 -1
  54. package/lib/re-export.js +0 -1
  55. package/lib/re-export.js.map +1 -1
  56. package/lib/reflection/methods/bind.d.ts +1 -1
  57. package/lib/reflection/methods/bind.d.ts.map +1 -1
  58. package/lib/schedule/extendable-timer.d.ts +1 -1
  59. package/lib/schedule/extendable-timer.d.ts.map +1 -1
  60. package/lib/schedule/extendable-timer.js.map +1 -1
  61. package/lib/schedule/interval.d.ts +1 -1
  62. package/lib/schedule/interval.d.ts.map +1 -1
  63. package/lib/schedule/timeout.d.ts +3 -3
  64. package/lib/schedule/timeout.d.ts.map +1 -1
  65. package/lib/schedule/timeout.js +6 -9
  66. package/lib/schedule/timeout.js.map +1 -1
  67. package/lib/state/simple-state-machine.d.ts +1 -1
  68. package/lib/state/simple-state-machine.d.ts.map +1 -1
  69. package/package.json +12 -11
  70. package/src/{autoindex.ts → autoindex.generated.ts} +12 -9
  71. package/src/debugging/inspect.ts +2 -1
  72. package/src/error/convert-unknown.ts +4 -0
  73. package/src/error/pretty.nodejs.ts +5 -1
  74. package/src/error/stack-parser.v8.ts +2 -2
  75. package/src/lifecycle/dispose/async-disposable.ts +25 -6
  76. package/src/lifecycle/dispose/bridges/native.ts +5 -7
  77. package/src/lifecycle/dispose/bridges/streams.ts +46 -36
  78. package/src/lifecycle/dispose/disposable.ts +94 -35
  79. package/src/lifecycle/dispose/sync-disposable.ts +21 -5
  80. package/src/lifecycle/event/event.ts +84 -37
  81. package/src/lifecycle/event/type.ts +2 -2
  82. package/src/map-and-set/required-map.ts +10 -0
  83. package/src/promise/deferred-promise.ts +5 -6
  84. package/src/promise/is-promise.ts +3 -0
  85. package/src/re-export.ts +0 -1
  86. package/src/schedule/extendable-timer.ts +1 -2
  87. package/src/schedule/timeout.ts +10 -6
  88. package/lib/autoindex.d.ts.map +0 -1
  89. package/lib/autoindex.js.map +0 -1
  90. package/lib/schedule/local-type.d.ts +0 -3
  91. package/lib/schedule/local-type.d.ts.map +0 -1
  92. package/lib/schedule/local-type.js +0 -2
  93. package/lib/schedule/local-type.js.map +0 -1
  94. package/src/schedule/local-type.ts +0 -2
  95. package/src/string/concatType.generator.ts +0 -31
@@ -2,37 +2,42 @@ import { defineInspectMethod } from '../../../debugging/inspect.js';
2
2
  import { objectName } from '../../../debugging/object-with-name.js';
3
3
  import type { IAsyncDisposable } from '../disposable.js';
4
4
 
5
- type ClosableAsync = {
6
- close(): Promise<any>;
7
- close(cb: (e?: Error) => void): any;
8
- };
5
+ type ClosableAsync =
6
+ | {
7
+ closed?: boolean;
8
+ close(): Promise<any>;
9
+ }
10
+ | {
11
+ closed?: boolean;
12
+ close(cb: (e?: Error) => void): any;
13
+ };
9
14
 
10
15
  /**
11
16
  * Convert "close()"-able object to disposable
17
+ *
18
+ * FIXME: 关闭事件不同步
19
+ *
12
20
  * @public
13
21
  */
14
22
  export function closableToDisposable<T extends ClosableAsync>(closable: T): IAsyncDisposable {
15
- const promised = closable.close.length === 0;
16
-
17
23
  return defineInspectMethod(
18
24
  {
19
25
  get displayName() {
20
26
  return `closable(${objectName(closable) || 'unknown'})`;
21
27
  },
22
28
  dispose(): Promise<void> {
23
- if (promised) {
24
- return Promise.resolve(closable.close()).then(() => undefined);
25
- } else {
26
- return new Promise<void>((resolve, reject) => {
27
- closable.close((error) => {
28
- if (error) {
29
- reject(error);
30
- } else {
31
- resolve();
32
- }
33
- });
29
+ return new Promise<void>((resolve, reject) => {
30
+ const mayPromise = closable.close((error) => {
31
+ if (error) {
32
+ reject(error);
33
+ } else {
34
+ resolve();
35
+ }
34
36
  });
35
- }
37
+ if (typeof mayPromise?.then === 'function') {
38
+ mayPromise.then(resolve, reject);
39
+ }
40
+ });
36
41
  },
37
42
  },
38
43
  (_depth, options) => {
@@ -41,37 +46,42 @@ export function closableToDisposable<T extends ClosableAsync>(closable: T): IAsy
41
46
  );
42
47
  }
43
48
 
44
- type EndableAsync = {
45
- end(): Promise<any>;
46
- end(cb: (e?: Error) => void): any;
47
- };
49
+ type EndableAsync =
50
+ | {
51
+ ended?: boolean;
52
+ end(): Promise<any>;
53
+ }
54
+ | {
55
+ ended?: boolean;
56
+ end(cb: (e?: Error) => void): any;
57
+ };
48
58
 
49
59
  /**
50
60
  * Convert "end()"-able object to disposable
61
+ *
62
+ * FIXME: 关闭事件不同步
63
+ *
51
64
  * @public
52
65
  */
53
66
  export function endableToDisposable<T extends EndableAsync>(endable: T): IAsyncDisposable {
54
- const promised = endable.end.length === 0;
55
-
56
67
  return defineInspectMethod(
57
68
  {
58
69
  get displayName() {
59
70
  return `endable(${objectName(endable) || 'unknown'})`;
60
71
  },
61
72
  dispose(): Promise<void> {
62
- if (promised) {
63
- return Promise.resolve(endable.end()).then(() => undefined);
64
- } else {
65
- return new Promise<void>((resolve, reject) => {
66
- return endable.end((error) => {
67
- if (error) {
68
- reject(error);
69
- } else {
70
- resolve();
71
- }
72
- });
73
+ return new Promise<void>((resolve, reject) => {
74
+ const mayPromise = endable.end((error) => {
75
+ if (error) {
76
+ reject(error);
77
+ } else {
78
+ resolve();
79
+ }
73
80
  });
74
- }
81
+ if (typeof mayPromise?.then === 'function') {
82
+ mayPromise.then(resolve, reject);
83
+ }
84
+ });
75
85
  },
76
86
  },
77
87
  (_depth, options) => {
@@ -1,7 +1,9 @@
1
1
  import { defineInspectMethod } from '../../debugging/inspect.js';
2
2
  import type { MaybeNamed } from '../../debugging/object-with-name.js';
3
+ import { convertCaughtError } from '../../error/convert-unknown.js';
4
+ import { prettyPrintError } from '../../error/pretty.nodejs.js';
3
5
  import { createStackTraceHolder, type StackTraceHolder } from '../../error/stack-trace.js';
4
- import { definePublicConstant } from '../../object/definePublicConstant.js';
6
+ import { isPromiseLike } from '../../promise/is-promise.js';
5
7
  import { Emitter } from '../event/event.js';
6
8
  import type { EventRegister } from '../event/type.js';
7
9
  import { fromNativeDisposable } from './bridges/native.js';
@@ -15,10 +17,14 @@ export enum DuplicateDisposeAction {
15
17
  }
16
18
 
17
19
  /** @public */
18
- export interface IDisposableEvents {
19
- onDisposeError: EventRegister<Error>;
20
- onBeforeDispose: EventRegister<void>;
21
- readonly hasDisposed: boolean;
20
+ export interface IDisposableEvents extends IBackReferenceDisposableEvent {
21
+ readonly onDisposeError: EventRegister<Error>;
22
+ readonly onPostDispose: EventRegister<void>;
23
+ readonly disposed: boolean;
24
+ }
25
+
26
+ export interface IBackReferenceDisposableEvent {
27
+ readonly onBeforeDispose: EventRegister<void>;
22
28
  }
23
29
 
24
30
  /** @public */
@@ -34,6 +40,22 @@ export interface IAsyncDisposable extends MaybeNamed {
34
40
  type _Type<Async extends boolean> = Async extends true ? IAsyncDisposable : IDisposable;
35
41
  type _RType<Async extends boolean> = Async extends true ? Promise<void> : void;
36
42
 
43
+ interface IDisposeState<Async extends boolean> {
44
+ /**
45
+ * 存在stack说明dispose已经开始(可能已经完成)
46
+ */
47
+ trace?: StackTraceHolder;
48
+ finished: boolean;
49
+ /**
50
+ * 同步的是undefined,异步的是Promise
51
+ */
52
+ result?: _RType<Async>;
53
+ /**
54
+ * 只有同步的用到,每次调用始终抛出相同错误,异步通过promise保存状态
55
+ */
56
+ error?: Error;
57
+ }
58
+
37
59
  /**
38
60
  * 增强型Disposable
39
61
  */
@@ -69,11 +91,11 @@ export abstract class AbstractEnhancedDisposable<Async extends boolean> implemen
69
91
  return `[Function debug]`;
70
92
  });
71
93
 
72
- this._onDisposeError = this._register(new Emitter<Error>(`${this.displayName}:errorEvent`));
94
+ this._onDisposeError = new Emitter<Error>(`${this.displayName}:errorEvent`, Emitter.EAction.PrintIgnore);
73
95
  this.onDisposeError = this._onDisposeError.register;
74
- this._onBeforeDispose = this._register(new Emitter<void>(`${this.displayName}:beforeEvent`));
96
+ this._onBeforeDispose = new Emitter<void>(`${this.displayName}:beforeEvent`, Emitter.EAction.PrintIgnore);
75
97
  this.onBeforeDispose = this._onBeforeDispose.register;
76
- this._onPostDispose = new Emitter<void>(`${this.displayName}:postEvent`);
98
+ this._onPostDispose = new Emitter<void>(`${this.displayName}:postEvent`, Emitter.EAction.PrintIgnore);
77
99
  this.onPostDispose = this._onPostDispose.register;
78
100
  }
79
101
 
@@ -81,8 +103,8 @@ export abstract class AbstractEnhancedDisposable<Async extends boolean> implemen
81
103
  * @throws if already disposed
82
104
  */
83
105
  public assertNotDisposed() {
84
- if (this._disposed) {
85
- throw new DuplicateDisposedError(this, this._disposed.trace);
106
+ if (this.__dispose_state.trace) {
107
+ throw new DuplicateDisposedError(this, this.__dispose_state.trace);
86
108
  }
87
109
  }
88
110
 
@@ -90,14 +112,15 @@ export abstract class AbstractEnhancedDisposable<Async extends boolean> implemen
90
112
  * register a disposable object
91
113
  */
92
114
  public _register<T extends _Type<Async>>(d: T): T;
93
- public _register<T extends _Type<Async> & IDisposableEvents>(d: T, autoDereference?: boolean): T;
115
+ public _register<T extends _Type<Async> & IBackReferenceDisposableEvent>(d: T, autoDereference?: boolean): T;
94
116
  public _register(d: any, autoDereference?: boolean): any {
95
117
  if (this._logger.enabled) this._logger(`register ${dispose_name(d)}`);
96
118
  this.assertNotDisposed();
97
119
  if (this._disposables.indexOf(d) !== -1) throw new Error(`disposable object ${dispose_name(d)} has already registed into "${dispose_name(this)}"`);
98
120
  this._disposables.unshift(fromNativeDisposable(d));
99
121
  if (autoDereference) {
100
- (d as IDisposableEvents).onBeforeDispose(() => {
122
+ (d as IBackReferenceDisposableEvent).onBeforeDispose(() => {
123
+ if (this.disposing || this.disposed) return;
101
124
  this._unregister(d);
102
125
  });
103
126
  }
@@ -117,52 +140,88 @@ export abstract class AbstractEnhancedDisposable<Async extends boolean> implemen
117
140
  return rmOk;
118
141
  }
119
142
 
120
- private _disposed?: {
121
- trace: StackTraceHolder;
122
- result: _RType<Async>;
123
- };
143
+ private __dispose_state: IDisposeState<Async> = { finished: false };
124
144
  public get disposed() {
125
- return !!this._disposed;
145
+ return this.__dispose_state.finished;
126
146
  }
147
+
127
148
  /**
128
- * @deprecated use disposed
149
+ * 正在dispose中(已开始但未完成)
129
150
  */
130
- public get hasDisposed() {
131
- return !!this._disposed;
151
+ public get disposing() {
152
+ return !this.__dispose_state.finished && !!this.__dispose_state.trace;
132
153
  }
133
154
 
134
155
  /**
135
156
  * 释放相关资源
136
157
  */
137
158
  public dispose(): _RType<Async> {
138
- if (this._disposed) {
139
- if (this.duplicateDispose === DuplicateDisposeAction.Allow) return this._disposed.result;
159
+ if (this.__dispose_state.trace) {
160
+ // 释放已开始或已结束
161
+ if (this.duplicateDispose === DuplicateDisposeAction.Allow) {
162
+ if (this.__dispose_state.error) {
163
+ throw this.__dispose_state.error;
164
+ } else {
165
+ /**
166
+ * biome-ignore lint/style/noNonNullAssertion: 完全无需考虑
167
+ *
168
+ * 异步dispose的同步部分,重复调用dispose,会返回undefined而非Promise
169
+ * 但这正好是希望的,否则死锁了
170
+ */
171
+ return this.__dispose_state.result!;
172
+ }
173
+ }
140
174
 
141
- const dupErr = new DuplicateDisposedError(this, this._disposed.trace);
175
+ const dupErr = new DuplicateDisposedError(this, this.__dispose_state.trace);
142
176
  dupErr.consoleWarning();
143
177
  if (this.duplicateDispose === DuplicateDisposeAction.Disable) {
144
178
  throw dupErr;
145
179
  } else {
146
- return this._disposed.result;
180
+ return this.__dispose_state.result as any;
147
181
  }
182
+ // never
148
183
  }
149
- this._onBeforeDispose.fireNoError();
150
184
 
151
- const r = this._dispose(this._disposables);
152
- const trace = createStackTraceHolder('disposed', this.dispose);
153
185
  const cleanup = () => {
154
- definePublicConstant(this, '_disposed', {
155
- // 记录 disposed 状态,顺便也记录调用栈
156
- trace: trace,
157
- result: r,
158
- });
186
+ this.__dispose_state.finished = true;
159
187
 
160
188
  Object.assign(this, { _disposables: null });
161
- this._onPostDispose.fireNoError();
189
+ this._onPostDispose.fire();
162
190
  this._onPostDispose.dispose();
191
+
192
+ this._onDisposeError.dispose();
163
193
  };
164
- if (r && 'then' in r) {
165
- r.finally(cleanup);
194
+
195
+ // 第一时间设置trace
196
+ this.__dispose_state.trace = createStackTraceHolder('disposed', this.dispose);
197
+
198
+ this._onBeforeDispose.fire();
199
+ this._onBeforeDispose.dispose();
200
+
201
+ try {
202
+ this.__dispose_state.result = this._dispose(this._disposables);
203
+ } catch (e) {
204
+ // 同步错误处理
205
+ const err = convertCaughtError(e);
206
+ this.__dispose_state.error = err;
207
+ this._onDisposeError.fire(err);
208
+ if (this._onDisposeError.listenerCount() === 0) {
209
+ prettyPrintError('unhandled sync dispose error', err);
210
+ }
211
+ cleanup();
212
+ throw this.__dispose_state.error;
213
+ }
214
+
215
+ const r = this.__dispose_state.result;
216
+ if (isPromiseLike(r)) {
217
+ // 异步错误处理
218
+ r.catch((e) => {
219
+ e = convertCaughtError(e);
220
+ this._onDisposeError.fire(e);
221
+ if (this._onDisposeError.listenerCount() === 0) {
222
+ prettyPrintError('unhandled async dispose error', e);
223
+ }
224
+ }).finally(cleanup);
166
225
  } else {
167
226
  cleanup();
168
227
  }
@@ -10,7 +10,7 @@ import { DuplicateDisposedError } from './disposedError.js';
10
10
  export abstract class DisposableOnce implements IDisposable {
11
11
  private _disposed?: StackTraceHolder;
12
12
 
13
- public get hasDisposed() {
13
+ public get disposed() {
14
14
  return !!this._disposed;
15
15
  }
16
16
  public dispose(): void {
@@ -32,21 +32,37 @@ export abstract class DisposableOnce implements IDisposable {
32
32
  * 完整版disposable类
33
33
  * 可以继承
34
34
  * 也可以直接用(相当于一个DisposableStack)
35
+ *
36
+ * 抛出异常的行为会延迟到所有资源都尝试释放完毕之后,因此显然只会抛出其中一个异常给dispose()的调用者
37
+ * 每一个资源释放失败,都会分别触发onDisposeError事件,如果此事件存在监听器,则最后不会抛出异常(处非监听器本身重新抛出)
38
+ * 不支持在dispose过程中添加onError事件监听器
39
+ *
35
40
  */
36
41
  export class EnhancedDisposable extends AbstractEnhancedDisposable<false> implements IDisposable {
37
42
  protected override _dispose(disposables: readonly IDisposable[]): void {
43
+ const hasListener = this._onDisposeError.listenerCount() > 0;
44
+
45
+ let lastError = null;
38
46
  for (const item of disposables.values()) {
39
47
  try {
40
48
  if (this._logger.enabled) this._logger(`dispose ${dispose_name(item)}`);
41
49
  item.dispose();
42
50
  } catch (e) {
43
- const ee = convertCaughtError(e);
44
- this._onDisposeError.fire(ee);
45
- if (!this._onDisposeError.listenerCount()) {
46
- console.error('Unhandled error during dispose: %s', ee.stack);
51
+ if (hasListener) {
52
+ try {
53
+ this._onDisposeError.fire(convertCaughtError(e));
54
+ } catch (e) {
55
+ lastError = e;
56
+ }
57
+ } else {
58
+ lastError = e;
47
59
  }
48
60
  }
49
61
  }
62
+
63
+ if (lastError) {
64
+ throw lastError;
65
+ }
50
66
  }
51
67
 
52
68
  /// compitable with stack
@@ -1,6 +1,7 @@
1
- import { Exit } from '@idlebox/errors';
2
1
  import { defineInspectMethod, inspectSymbol } from '../../debugging/inspect.js';
3
2
  import { nameObject, objectName } from '../../debugging/object-with-name.js';
3
+ import { convertCaughtError } from '../../error/convert-unknown.js';
4
+ import { prettyPrintError } from '../../error/pretty.nodejs.js';
4
5
  import { createStackTraceHolder } from '../../error/stack-trace.js';
5
6
  import { functionToDisposable } from '../dispose/bridges/function.js';
6
7
  import type { IDisposable } from '../dispose/disposable.js';
@@ -9,15 +10,27 @@ import type { EventHandler, EventRegister, IEventEmitter } from './type.js';
9
10
 
10
11
  const anonymousName = 'AnonymousEmitter';
11
12
 
13
+ enum FireErrorAction {
14
+ Throw = 0,
15
+ Delay = 1,
16
+ Ignore = 2,
17
+ PrintIgnore = 3,
18
+ }
19
+
12
20
  /**
13
21
  * @public
14
22
  */
15
23
  export class Emitter<T = unknown> implements IEventEmitter<T> {
16
- protected _callbacks?: (EventHandler<T> | undefined)[];
24
+ protected readonly _callbacks: (EventHandler<T> | undefined)[] = [];
17
25
  private executing = false;
18
26
  private _something_change_during_call = false;
19
27
 
20
- constructor(public readonly displayName: string = anonymousName) {
28
+ static readonly EAction = FireErrorAction;
29
+
30
+ constructor(
31
+ public readonly displayName: string = anonymousName,
32
+ private readonly onErrorDefault: FireErrorAction = FireErrorAction.Throw,
33
+ ) {
21
34
  this.handle = Object.defineProperties(this.handle.bind(this), {
22
35
  once: {
23
36
  get: () => this.once.bind(this),
@@ -25,7 +38,7 @@ export class Emitter<T = unknown> implements IEventEmitter<T> {
25
38
  wait: {
26
39
  get: () => this.wait.bind(this),
27
40
  },
28
- hasDisposed: {
41
+ disposed: {
29
42
  get: () => this._disposed,
30
43
  },
31
44
  });
@@ -35,17 +48,69 @@ export class Emitter<T = unknown> implements IEventEmitter<T> {
35
48
  }
36
49
 
37
50
  public listenerCount() {
38
- return this._callbacks?.length ?? 0;
51
+ return this._callbacks.length;
52
+ }
53
+
54
+ private __fireThrow(data: T) {
55
+ for (const callback of this._callbacks) {
56
+ callback?.(data);
57
+ }
58
+ }
59
+ private __fireDelay(data: T) {
60
+ const errors: Error[] = [];
61
+ for (const callback of this._callbacks) {
62
+ try {
63
+ callback?.(data);
64
+ } catch (e) {
65
+ errors.push(convertCaughtError(e));
66
+ }
67
+ }
68
+ return errors;
69
+ }
70
+ private __fireIgnore(data: T) {
71
+ for (const callback of this._callbacks) {
72
+ try {
73
+ callback?.(data);
74
+ } catch {}
75
+ }
76
+ }
77
+ private __firePrintIgnore(data: T) {
78
+ for (const callback of this._callbacks) {
79
+ try {
80
+ callback?.(data);
81
+ } catch (e) {
82
+ const ee = convertCaughtError(e);
83
+ prettyPrintError('error while handling event', ee);
84
+ }
85
+ }
39
86
  }
40
87
 
41
- public fire(data: T) {
88
+ /**
89
+ * @param data
90
+ * @param error {Emitter.EAction} 如何处理错误
91
+ * - Throw: 默认行为,遇到错误立即抛出,后续监听器不再被调用
92
+ * - Delay: 等所有监听器都调用完后,如果有错误则抛出AggregateError,包含所有错误
93
+ * - Ignore: 忽略所有错误,继续调用全部监听器
94
+ * - PrintIgnore: 忽略所有错误,但打印错误信息
95
+ * @returns
96
+ */
97
+ public fire(data: T, error = this.onErrorDefault) {
42
98
  this.requireNotExecuting();
43
- if (!this._callbacks) return;
99
+ if (!this._callbacks.length) return;
44
100
 
45
101
  this.executing = true;
46
102
  try {
47
- for (const callback of this._callbacks) {
48
- callback?.(data);
103
+ if (error === FireErrorAction.Throw) {
104
+ this.__fireThrow(data);
105
+ } else if (error === FireErrorAction.Delay) {
106
+ const errors = this.__fireDelay(data);
107
+ if (errors.length) {
108
+ throw new AggregateError(errors, 'multiple errors while handling event');
109
+ }
110
+ } else if (error === FireErrorAction.Ignore) {
111
+ this.__fireIgnore(data);
112
+ } else if (error === FireErrorAction.PrintIgnore) {
113
+ this.__firePrintIgnore(data);
49
114
  }
50
115
  } finally {
51
116
  this.executing = false;
@@ -55,25 +120,9 @@ export class Emitter<T = unknown> implements IEventEmitter<T> {
55
120
  }
56
121
  }
57
122
 
123
+ /** @deprecated use fire(data, Emitter.Error.Ignore) */
58
124
  public fireNoError(data: T) {
59
- this.requireNotExecuting();
60
- if (!this._callbacks) return;
61
-
62
- this.executing = true;
63
- for (const callback of this._callbacks) {
64
- try {
65
- callback?.(data);
66
- } catch (e) {
67
- if (e instanceof Exit) {
68
- break;
69
- }
70
- console.error('Emitter.fireNoError: error ignored: %s', e instanceof Error ? e.stack : e);
71
- }
72
- }
73
- this.executing = false;
74
- if (this._something_change_during_call) {
75
- this.checkDeleted();
76
- }
125
+ this.fire(data, FireErrorAction.Ignore);
77
126
  }
78
127
 
79
128
  get register(): EventRegister<T> {
@@ -91,20 +140,17 @@ export class Emitter<T = unknown> implements IEventEmitter<T> {
91
140
  handle(callback: EventHandler<T>): IDisposable {
92
141
  this.requireNotExecuting();
93
142
 
94
- if (!this._callbacks) this._callbacks = [];
95
-
96
- const callbacks = this._callbacks;
97
- callbacks.unshift(callback);
143
+ this._callbacks.unshift(callback);
98
144
 
99
145
  return functionToDisposable(
100
146
  nameObject(`removeListener(${objectName(callback)})`, () => {
101
- const index = callbacks.indexOf(callback);
147
+ const index = this._callbacks.indexOf(callback);
102
148
  if (index === -1) return;
103
149
  if (this.executing) {
104
150
  this._something_change_during_call = true;
105
- callbacks[index] = undefined;
151
+ this._callbacks[index] = undefined;
106
152
  } else {
107
- callbacks.splice(index, 1);
153
+ this._callbacks.splice(index, 1);
108
154
  }
109
155
  }),
110
156
  );
@@ -147,7 +193,7 @@ export class Emitter<T = unknown> implements IEventEmitter<T> {
147
193
  }
148
194
 
149
195
  private _disposed = false;
150
- public get hasDisposed() {
196
+ public get disposed() {
151
197
  return this._disposed;
152
198
  }
153
199
 
@@ -159,7 +205,8 @@ export class Emitter<T = unknown> implements IEventEmitter<T> {
159
205
  if (this._disposed) return;
160
206
  this._disposed = true;
161
207
 
162
- this._callbacks = undefined;
208
+ this._callbacks.length = 0;
209
+ Object.assign(this, { _callbacks: null });
163
210
 
164
211
  if (this._waittings) {
165
212
  for (const rej of this._waittings) {
@@ -169,7 +216,7 @@ export class Emitter<T = unknown> implements IEventEmitter<T> {
169
216
 
170
217
  this._waittings = undefined;
171
218
 
172
- const trace = createStackTraceHolder('disposed');
219
+ const trace = createStackTraceHolder('disposed', this.dispose);
173
220
 
174
221
  const makeUnCallable = (name: string) => {
175
222
  Object.assign(this, {
@@ -53,7 +53,7 @@ export interface IEventEmitter<T = unknown> extends IDisposable {
53
53
  */
54
54
  wait(): Promise<T>;
55
55
 
56
- readonly hasDisposed: boolean;
56
+ readonly disposed: boolean;
57
57
  }
58
58
 
59
59
  export type EventHandler<T> = (data: T) => void;
@@ -65,7 +65,7 @@ export interface EventRegister<T> {
65
65
  (callback: EventHandler<T>): IDisposable;
66
66
  once(callback: EventHandler<T>): IDisposable;
67
67
  wait(): IDisposable;
68
- readonly hasDisposed: boolean;
68
+ readonly disposed: boolean;
69
69
  }
70
70
 
71
71
  export type EventEmitterMap<T extends Record<string, unknown>> = {
@@ -37,6 +37,10 @@ export class RequiredMap<K, V> extends Map<K, V> {
37
37
  }
38
38
  }
39
39
 
40
+ if (Map.prototype.getOrInsert) {
41
+ RequiredMap.prototype.get = Map.prototype.getOrInsert as any;
42
+ }
43
+
40
44
  /**
41
45
  * A map that holds instances, automatically create new instance
42
46
  */
@@ -56,3 +60,9 @@ export abstract class InstanceMap<K, V> extends Map<K, V> {
56
60
  return nv;
57
61
  }
58
62
  }
63
+
64
+ if (typeof Map.prototype.getOrInsertComputed === 'function') {
65
+ RequiredMap.prototype.get = function (this: InstanceMap<any, any>, id: any): any {
66
+ return this.getOrInsertComputed(id, (k) => this.instance(k));
67
+ };
68
+ }
@@ -1,6 +1,5 @@
1
1
  import { CanceledError, TimeoutError } from '@idlebox/errors';
2
2
  import type { IDisposable } from '../lifecycle/dispose/disposable.js';
3
- import type { TimeoutType } from '../schedule/local-type.js';
4
3
  import { scheduler } from '../schedule/scheduler.js';
5
4
 
6
5
  export type ValueCallback<T = any> = (value: T | Promise<T>) => void;
@@ -143,16 +142,16 @@ export class DeferredPromise<T, PT = any> {
143
142
  };
144
143
  }
145
144
 
146
- #timer?: TimeoutType;
145
+ private timer?: ITimeoutType;
147
146
  #cancel_timeout() {
148
- if (this.#timer) {
149
- clearTimeout(this.#timer);
150
- this.#timer = undefined;
147
+ if (this.timer) {
148
+ clearTimeout(this.timer);
149
+ this.timer = undefined;
151
150
  }
152
151
  }
153
152
  timeout(ms: number) {
154
153
  if (this.settled) throw new Error('no more timeout after settled');
155
- this.#timer = setTimeout(() => {
154
+ this.timer = setTimeout(() => {
156
155
  this.error(new TimeoutError(ms, 'promise not settled'));
157
156
  }, ms);
158
157
 
@@ -0,0 +1,3 @@
1
+ export function isPromiseLike(object: unknown): object is PromiseLike<unknown> {
2
+ return typeof object === 'object' && object !== null && typeof (object as any).then === 'function';
3
+ }
package/src/re-export.ts CHANGED
@@ -1,4 +1,3 @@
1
- /// <reference types="@idlebox/itypes" />
2
1
  /// <reference types="debug" />
3
2
 
4
3
  export * from '@idlebox/errors';
@@ -1,12 +1,11 @@
1
1
  import { DeferredPromise } from '../promise/deferred-promise.js';
2
- import type { TimeoutType } from './local-type.js';
3
2
 
4
3
  /**
5
4
  * 反复推迟的 setTimeout
6
5
  */
7
6
  export class ExtendableTimer {
8
7
  private readonly dfd = new DeferredPromise<void>();
9
- private tmr?: TimeoutType;
8
+ public tmr?: ITimeoutType;
10
9
 
11
10
  constructor(private readonly durationMs: number) {}
12
11