Skip to content

Instantly share code, notes, and snippets.

@julien-f
Created April 4, 2019 11:01
Show Gist options
  • Save julien-f/37ac8e8ba1036520c21516723cbb731d to your computer and use it in GitHub Desktop.
Save julien-f/37ac8e8ba1036520c21516723cbb731d to your computer and use it in GitHub Desktop.
const Signal = require('./Signal.js')
// Creation
{
const source = Signal.source()
source.dispatch()
source.signal.dispatched
}
{
const signal = new Signal(dispatch => {
dispatch()
})
signal.dispatched
}
// Consumption
{
// sync inspection
if (signal.dispatched) {
console.log('signal dispatched')
}
try {
signal.throwIfDispatched()
} catch (error) {
console.log('signal dispatched')
}
}
{
// sync listener
const subscription = signal.subscribe(() => {
console.log('signal dispatched')
})
subscription.unsubscribe()
}
{
// promise API
signal.then(() => {
console.log('signal dispatched')
})
}
function dispatch() {
if (this._dispatched) {
return
}
this._dispatched = true
const resolve = this._resolve
if (resolve !== undefined) {
this._resolve = undefined
resolve()
}
const listeners = this._listeners
if (listeners !== undefined) {
this._listeners = undefined
for (let i = 0, n = listeners.length; i < n; ++i) {
listeners[i].call(this)
}
}
}
const INTERNAL = {}
function Source(signals) {
const dispatch_ = (this.dispatch = dispatch.bind(
(this.signal = new Signal(INTERNAL))
))
if (signals === undefined) {
return
}
const n = signals.length
for (let i = 0; i < n; ++i) {
if (signals[i].dispatched) {
dispatch_()
return
}
}
for (let i = 0; i < n; ++i) {
signals[i].addListener(dispatch_)
}
}
class Subscription {
constructor(signal, listener) {
this._listener = listener
this._signal = signal
}
get closed() {
return this._signal === undefined
}
unsubscribe() {
const signal = this._signal
if (signal !== undefined) {
const listener = this._listener
this._listener = this._signal = undefined
const listeners = signal._listeners
if (listeners !== undefined) {
const i = listeners.indexOf(listener)
if (i !== -1) {
listeners.splice(i, 1)
}
}
}
}
}
const closedSubscription = new Subscription()
class Signal {
static source(signals) {
return new Source(signals)
}
constructor(executor) {
this._listeners = undefined
this._promise = undefined
this._dispatched = false
this._resolve = undefined
if (executor !== INTERNAL) {
executor(dispatch.bind(this))
}
}
get description() {
return this._description
}
get dispatched() {
return this._dispatched
}
throwIfDispatched() {
if (this._dispatched) {
throw new Error('this signal has been dispatched')
}
}
// ===========================================================================
// Promise like API
// ===========================================================================
then(listener) {
if (typeof listener !== 'function') {
return this
}
let promise = this._promise
if (promise === undefined) {
const dispatched = this._dispatched
promise = this._promise = dispatched
? Promise.resolve()
: new Promise(resolve => {
this._resolve = resolve
})
}
return promise.then(listener)
}
// ===========================================================================
// Observable like API (but not compatible)
// ===========================================================================
subscribe(listener) {
if (this._dispatched) {
listener.call(this)
return closedSubscription
}
const listeners = this._listeners
if (listeners === undefined) {
this._listeners = [listener]
} else {
listeners.push(listener)
}
return new Subscription(this, listener)
}
}
module.exports = Signal
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment