Files
airllm-fork-nodejs/node_modules/lifecycle-utils/dist/State.js
2026-02-05 15:27:49 +08:00

180 lines
6.7 KiB
JavaScript

/**
* `State` is a utility class that allows you to hold a value and notify listeners when the value changes.
*/
export class State {
/** @internal */ _queueEvents;
/** @internal */ _listenerCallbacks;
/** @internal */ _state;
/** @internal */ _changeEventMicrotaskQueued;
/**
* @param defaultState
* @param [options]
* @param [options.queueEvents] - queue events to be dispatched in a microtask.
* If the state changes multiple times in the same microtask, only the last change will be dispatched.
* If the most recent value is the same as the previous value, no event will be dispatched.
* Set this to `false` to dispatch events immediately upon state changes.
*/
constructor(defaultState, { queueEvents = true } = {}) {
this._queueEvents = queueEvents;
this._listenerCallbacks = new Map();
this._state = defaultState;
this._changeEventMicrotaskQueued = false;
this.createChangeListener = this.createChangeListener.bind(this);
this.clearChangeListeners = this.clearChangeListeners.bind(this);
}
get state() {
return this._state;
}
set state(newState) {
if (this._state === newState)
return;
this._state = newState;
if (!this._queueEvents) {
this._dispatchChangeEvent(this._state);
}
else if (!this._changeEventMicrotaskQueued) {
this._changeEventMicrotaskQueued = true;
(globalThis.queueMicrotask || globalThis.setTimeout)(() => {
this._changeEventMicrotaskQueued = false;
this._dispatchChangeEvent(this._state);
});
}
}
createChangeListener(callback, callInstantlyWithCurrentState = false) {
this._listenerCallbacks.set(callback, this._state);
if (callInstantlyWithCurrentState) {
try {
callback(this._state, undefined);
}
catch (err) {
console.error(err);
}
}
return StateChangeListenerHandle._create(() => {
this._listenerCallbacks.delete(callback);
});
}
clearChangeListeners() {
this._listenerCallbacks.clear();
}
get changeListenerCount() {
return this._listenerCallbacks.size;
}
/** @internal */
_dispatchChangeEvent(newState) {
for (const [listenerCallback, lastValue] of Array.from(this._listenerCallbacks.entries())) {
if (lastValue === newState)
continue;
if (this._listenerCallbacks.has(listenerCallback))
this._listenerCallbacks.set(listenerCallback, newState);
try {
listenerCallback(newState, lastValue);
}
catch (err) {
console.error(err);
}
}
}
/**
* Create a listener that listens to multiple states and calls the callback when any of the states change.
*
* For example,
* ```typescript
* import {State} from "lifecycle-utils";
*
* const valueState1 = new State<number>(6);
* const valueState2 = new State<string>("hello");
* const valueState3 = new State<boolean>(true);
*
* const eventHandle = State.createCombinedChangeListener([valueState1, valueState2, valueState3], (newValues, previousValues) => {
* console.log("new values:", newValues);
* console.log("previous values:", previousValues);
* });
*
* valueState1.state = 7;
* valueState2.state = "world";
* valueState3.state = false;
*
* // after a microtask, the listener will be called
* // to make event fire immediately upon change, disable the `queueEvents` option on the constructor
* await new Promise(resolve => setTimeout(resolve, 0));
* // will print:
* // new values: [7, "world", false]
* // previous values: [6, "hello", true]
*
* eventHandle.dispose();
* ```
* @param states
* @param callback
* @param [options]
* @param [options.callInstantlyWithCurrentState]
* @param [options.queueEvents] - queue events to be dispatched in a microtask.
* If the state changes multiple times in the same microtask, only the last change will be dispatched.
* If the most recent value is the same as the previous value, no event will be dispatched.
* Set this to `false` to dispatch events immediately upon state changes.
*/
static createCombinedChangeListener(states, callback, { callInstantlyWithCurrentState = false, queueEvents = true } = {}) {
let changeEventMicrotaskQueued = false;
const getState = () => states.map((state) => state.state);
let lastDispatchState = getState();
const dispatchEvent = (onlyIfChanged = true, includeLastState = true) => {
const newState = getState();
const previousState = lastDispatchState;
if (onlyIfChanged &&
newState.every((value, index) => value === previousState[index]))
return;
lastDispatchState = newState;
try {
callback(newState, includeLastState
? previousState
: previousState.map(() => undefined));
}
catch (err) {
console.error(err);
}
};
const onChange = () => {
if (changeEventMicrotaskQueued)
return;
if (!queueEvents)
dispatchEvent();
else {
changeEventMicrotaskQueued = true;
(globalThis.queueMicrotask || globalThis.setTimeout)(() => {
changeEventMicrotaskQueued = false;
dispatchEvent();
});
}
};
const handlers = states.map((state) => state.createChangeListener(onChange, false));
if (callInstantlyWithCurrentState)
dispatchEvent(false, false);
return StateChangeListenerHandle._create(() => handlers.forEach((handler) => handler.dispose()));
}
}
export class StateChangeListenerHandle {
/** @internal */
_dispose;
constructor(dispose) {
this._dispose = dispose;
this.dispose = this.dispose.bind(this);
this[Symbol.dispose] = this[Symbol.dispose].bind(this);
}
dispose() {
if (this._dispose != null) {
this._dispose();
this._dispose = null;
}
}
[Symbol.dispose]() {
this.dispose();
}
get disposed() {
return this._dispose == null;
}
/** @internal */
static _create(dispose) {
return new StateChangeListenerHandle(dispose);
}
}
//# sourceMappingURL=State.js.map