mini-state-machine
Version:
A miniature state machine
114 lines (113 loc) • 3.84 kB
JavaScript
import { find } from "./array.js";
var EventType;
(function (EventType) {
EventType["State"] = "s";
EventType["Transition"] = "t";
})(EventType || (EventType = {}));
const EVENT_TYPE_STATE_REXP = /^(enter|leave)/;
const EVENT_TYPE_TRANSITION_REXP = /^(before|after)/;
function callbackToPromise(callback, options) {
let output, uncaught = null;
try {
output = callback(options);
}
catch (err) {
uncaught = err;
output = false;
}
return Promise.resolve(output)
.catch(err => {
uncaught = err;
return false;
})
.then(result => {
if (uncaught !== null) {
setTimeout(function __throwUnhandledException() {
throw uncaught;
}, 0);
}
return result;
});
}
export function createEventsInterface() {
const handlers = [];
const idleCallbacks = [];
const events = {
"@@handlers": handlers,
add: (event, stateOrTransition, callback, { once = false } = {}) => {
handlers.push({
event,
type: resolveEventType(event),
value: stateOrTransition,
callback,
once
});
return {
remove: () => events.remove(event, stateOrTransition, callback)
};
},
addIdle: callback => {
idleCallbacks.push(callback);
return {
remove: () => {
const ind = idleCallbacks.indexOf(callback);
if (ind >= 0) {
idleCallbacks.splice(ind, 1);
}
}
};
},
emitIdle: (target) => {
if (target) {
return callbackToPromise(target).then(() => { });
}
if (idleCallbacks.length <= 0)
return Promise.resolve();
return Promise.all(idleCallbacks.map(callback => callbackToPromise(callback))).then(() => { });
},
execute: (event, stateOrTransition, options) => {
const type = resolveEventType(event);
const work = handlers.filter(item => item.type === type &&
item.event === event &&
(item.value === stateOrTransition || item.value === "*"));
return (function doNext() {
const item = work.shift();
if (!item) {
return Promise.resolve();
}
return callbackToPromise(item.callback, options).then(result => {
if (item.once === true) {
events.remove(event, stateOrTransition, item.callback);
}
if (result === false) {
// cancel transition
return false;
}
return doNext();
});
})();
},
remove: (event, stateOrTransition, callback) => {
const type = resolveEventType(event);
const handerInd = find(handlers, item => {
return (item.event === event &&
item.type === type &&
(item.value === stateOrTransition || item.value === "*") &&
item.callback === callback);
}, { index: true });
if (handerInd >= 0) {
handlers.splice(handerInd, 1);
}
}
};
return events;
}
function resolveEventType(event) {
if (EVENT_TYPE_STATE_REXP.test(event)) {
return EventType.State;
}
else if (EVENT_TYPE_TRANSITION_REXP.test(event)) {
return EventType.Transition;
}
throw new Error(`Failed resolving event type: unrecognised prefix: ${event}`);
}