Skip to content

Instantly share code, notes, and snippets.

@dfkaye
Last active August 5, 2023 05:08
Show Gist options
  • Save dfkaye/b6797c3887139cc5d1c56511514f3294 to your computer and use it in GitHub Desktop.
Save dfkaye/b6797c3887139cc5d1c56511514f3294 to your computer and use it in GitHub Desktop.
signal - implementation as a named event emitter vs. implementation as reactive-observable value (and value change)
// 20 July 2023
// In progress... may delete and start over... may just blog about the
// philosophical confusion that Signals have generated in the web
// development world, something easily sewn...
// Signal, an implementation that accepts a name for itself,
// and an optional `assign` method for binding the signal to
// a parent object (a feature which has little to do with the
// nature of Signals...)
// Signals, because they are not a singularly defined pattern,
// fall under different categories. One version is the reactive-observable
// value recently popularized by SignalsJS (if I have that right)
// that has propagated to other front-end frameworks (preactJS,
// angular, maybe marko, maybe vueJS).
// This version of a signal is the event emitter which is defined
// as an object under a parent object, accessed by its name on that
// object (e.g., object.update for a signal that issues `update`
// events.
//
function Signal(type) {
if (!(this instanceof Signal)) {
return Signal.create(type);
}
this.listeners = new Set();
this.signal = type;
return {
[type]: Object.freeze(this),
assign: function (parent) {
this.assign = void 0;
return Object.assign(parent, this);
}
};
}
Signal.create = function (type) {
return new Signal(type);
};
var SP = Signal.prototype;
SP.addListener = function (fn) {
return typeof fn == 'function' && this.listeners.add(fn);
};
SP.removeListener = function (fn) {
return typeof fn == 'function' && this.listeners.delete(fn);
};
SP.dispatch = function (e) {
if (!(this instanceof Signal)) {
var s = Object.prototype.toString.call(this);
var t = s.substring(8, s.length - 1);
var e = TypeError(`dispatch called on a non-Signal object of type <${ t }>.`);
return console.error(e);
}
var signal = this.signal;
var args = Object.assign({}, { message, data } = e);
var dispatch = function (fn) { fn.apply({ signal }, [args]); };
this.listeners.forEach(dispatch);
};
/* test it out */
var test = {};
// Use either of this approaches to attach the signal to a parent
// using its name as the access property.
Object.assign(test, Signal('Q'));
Signal('Q').assign(test);
// Once a signal is assigned a parent, it cannot be assigned again.
console.assert(!('assign' in test.Q), "shouldn't have an 'assign' method")
// now add some listener functions
test.Q.addListener(function (e) {
console.assert(this.signal === 'Q' )
console.log('a:', e);
});
test.Q.addListener(function (e) {
console.assert(this.signal === 'Q');
console.log('b:', e);
});
// should work with data
test.Q.dispatch({ message: 'test', data: [1,2,3] });
// should work with no data
test.Q.dispatch({ message: 'empty' });
// shouldn't be faked
test.Q.dispatch.call({ listeners: [...test.Q.listeners], signal: "Q"}, { message: "fake" });
// should fail
test.Q.dispatch.call({ listeners: [function(e) {console.assert(0, "shouldn't see this assertion")}], signal: "R"}, { message: "fake" });
/* another one with another parent */
var parent = { name: "Q" };
Signal('Q').assign(parent);
function A(e) {
console.log("A:", e);
}
parent.Q.addListener(A);
// should work
parent.Q.dispatch({ message: "signal Q on parent" });
// should fail
parent.Q.dispatch.call({ listeners: [A], signal: "Q"}, { message: "should fail" });
parent.Q.removeListener(A);
parent.Q.dispatch({ message: "shouldn't see this" });
/*
allowing assignment to multiple parents means listeners on a signal are
called on multiple parents each time
*/
/*
var count = 0;
var s = Signal('S');
s.assign = function (parent) {
// this.assign = void 0;
return Object.assign(parent, this);
}
var t = { name: 'test' };
var u = { name: 'stuy' };
s.assign(t);
t.S.addListener(function(e) {
console.log("t", count += 1, e)
});
s.assign(u);
u.S.addListener(function(e) {
console.log("u", count += 1, e)
});
t.S.dispatch({message: "message from t.S"});
u.S.dispatch({message: "message from u.S"});
*/
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment