Last active
August 5, 2023 05:08
-
-
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)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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