John Cowen 01d1b3c139 ui: Small EventSource additions (#5978)
1. EventSources now pass themselves thorugh to the run function as a
second argument. This enables the use of arrow functions along with the
EventSources API
`(configuration, source) => source.close()`. Order of arguments could
potentially be switched at a later date.
2. BlockingEventSources now let you pass an 'event' through at
instantation time. If you do this, the event will immediately be dispatched
once the EventSource is opened. The usecase for this is for 'unfreezing' cached
BlockingEvents. This makes it easier to provide a cache for
BlockingEventSources by caching its data rather than the entire
BlockingEventSource itself.

```
new BlockingEventSource(
  (config, source) => { /* something */ },
  {
    cursor: 1024, // this would also come from a cache
    currentEvent: getFromSomeSortOfCache(eventSourceId) //this is the new bit
  }
);

// more realistically

new BlockingEventSource(
  (config, source) => { return data.findSomething(slug, config) },
  getFromSomeSortOfCache(eventSourceId)
);

```
2019-09-04 08:35:05 +00:00

82 lines
2.2 KiB
JavaScript

export const defaultRunner = function(target, configuration, isClosed) {
if (isClosed(target)) {
target.dispatchEvent({ type: 'close' });
return;
}
// TODO Consider wrapping this is a promise for none thenable returns
return target.source
.bind(target)(configuration, target)
.then(function(res) {
return defaultRunner(target, configuration, isClosed);
});
};
const errorEvent = function(e) {
return new ErrorEvent('error', {
error: e,
message: e.message,
});
};
const isClosed = function(target) {
switch (target.readyState) {
case 2: // CLOSED
case 3: // CLOSING
return true;
}
return false;
};
export default function(
EventTarget,
P = Promise,
run = defaultRunner,
createErrorEvent = errorEvent
) {
return class extends EventTarget {
constructor(source, configuration = {}) {
super();
this.readyState = 2;
this.source =
typeof source !== 'function'
? function(configuration, target) {
this.close();
return P.resolve();
}
: source;
this.readyState = 0; // CONNECTING
P.resolve()
.then(() => {
// if we are already closed, don't do anything
if (this.readyState > 1) {
return;
}
this.readyState = 1; // open
// the connection _was just_ opened
this.dispatchEvent({ type: 'open' });
return run(this, configuration, isClosed);
})
.catch(e => {
this.dispatchEvent(createErrorEvent(e));
// close after the dispatch so we can tell if it was an error whilst closed or not
// but make sure its before the promise tick
this.readyState = 2; // CLOSE
})
.then(() => {
// This only gets called when the promise chain completely finishes
// so only when its completely closed.
this.readyState = 2; // CLOSE
});
}
close() {
// additional readyState 3 = CLOSING
switch (this.readyState) {
case 2: // CLOSED
// it's already CLOSED , do nothing
break;
default:
this.readyState = 3; // CLOSING
}
// non-standard
return this;
}
};
}