ran-boilerplate
Version:
React . Apollo (GraphQL) . Next.js Toolkit
217 lines (215 loc) • 7.27 kB
JavaScript
/**
* Helper to make a Subscribe function (just like Promise helps make a
* Thenable).
*
* @param executor Function which can make calls to a single Observer
* as a proxy.
* @param onNoObservers Callback when count of Observers goes to zero.
*/
export function createSubscribe(executor, onNoObservers) {
var proxy = new ObserverProxy(executor, onNoObservers);
return proxy.subscribe.bind(proxy);
}
/**
* Implement fan-out for any number of Observers attached via a subscribe
* function.
*/
var ObserverProxy = /** @class */ (function () {
/**
* @param executor Function which can make calls to a single Observer
* as a proxy.
* @param onNoObservers Callback when count of Observers goes to zero.
*/
function ObserverProxy(executor, onNoObservers) {
var _this = this;
this.observers = [];
this.unsubscribes = [];
this.observerCount = 0;
// Micro-task scheduling by calling task.then().
this.task = Promise.resolve();
this.finalized = false;
this.onNoObservers = onNoObservers;
// Call the executor asynchronously so subscribers that are called
// synchronously after the creation of the subscribe function
// can still receive the very first value generated in the executor.
this.task
.then(function () {
executor(_this);
})
.catch(function (e) {
_this.error(e);
});
}
ObserverProxy.prototype.next = function (value) {
this.forEachObserver(function (observer) {
observer.next(value);
});
};
ObserverProxy.prototype.error = function (error) {
this.forEachObserver(function (observer) {
observer.error(error);
});
this.close(error);
};
ObserverProxy.prototype.complete = function () {
this.forEachObserver(function (observer) {
observer.complete();
});
this.close();
};
/**
* Subscribe function that can be used to add an Observer to the fan-out list.
*
* - We require that no event is sent to a subscriber sychronously to their
* call to subscribe().
*/
ObserverProxy.prototype.subscribe = function (nextOrObserver, error, complete) {
var _this = this;
var observer;
if (nextOrObserver === undefined &&
error === undefined &&
complete === undefined) {
throw new Error('Missing Observer.');
}
// Assemble an Observer object when passed as callback functions.
if (implementsAnyMethods(nextOrObserver, ['next', 'error', 'complete'])) {
observer = nextOrObserver;
}
else {
observer = {
next: nextOrObserver,
error: error,
complete: complete
};
}
if (observer.next === undefined) {
observer.next = noop;
}
if (observer.error === undefined) {
observer.error = noop;
}
if (observer.complete === undefined) {
observer.complete = noop;
}
var unsub = this.unsubscribeOne.bind(this, this.observers.length);
// Attempt to subscribe to a terminated Observable - we
// just respond to the Observer with the final error or complete
// event.
if (this.finalized) {
this.task.then(function () {
try {
if (_this.finalError) {
observer.error(_this.finalError);
}
else {
observer.complete();
}
}
catch (e) {
// nothing
}
return;
});
}
this.observers.push(observer);
return unsub;
};
// Unsubscribe is synchronous - we guarantee that no events are sent to
// any unsubscribed Observer.
ObserverProxy.prototype.unsubscribeOne = function (i) {
if (this.observers === undefined || this.observers[i] === undefined) {
return;
}
delete this.observers[i];
this.observerCount -= 1;
if (this.observerCount === 0 && this.onNoObservers !== undefined) {
this.onNoObservers(this);
}
};
ObserverProxy.prototype.forEachObserver = function (fn) {
if (this.finalized) {
// Already closed by previous event....just eat the additional values.
return;
}
// Since sendOne calls asynchronously - there is no chance that
// this.observers will become undefined.
for (var i = 0; i < this.observers.length; i++) {
this.sendOne(i, fn);
}
};
// Call the Observer via one of it's callback function. We are careful to
// confirm that the observe has not been unsubscribed since this asynchronous
// function had been queued.
ObserverProxy.prototype.sendOne = function (i, fn) {
var _this = this;
// Execute the callback asynchronously
this.task.then(function () {
if (_this.observers !== undefined && _this.observers[i] !== undefined) {
try {
fn(_this.observers[i]);
}
catch (e) {
// Ignore exceptions raised in Observers or missing methods of an
// Observer.
// Log error to console. b/31404806
if (typeof console !== 'undefined' && console.error) {
console.error(e);
}
}
}
});
};
ObserverProxy.prototype.close = function (err) {
var _this = this;
if (this.finalized) {
return;
}
this.finalized = true;
if (err !== undefined) {
this.finalError = err;
}
// Proxy is no longer needed - garbage collect references
this.task.then(function () {
_this.observers = undefined;
_this.onNoObservers = undefined;
});
};
return ObserverProxy;
}());
/** Turn synchronous function into one called asynchronously. */
export function async(fn, onError) {
return function () {
var args = [];
for (var _i = 0; _i < arguments.length; _i++) {
args[_i] = arguments[_i];
}
Promise.resolve(true)
.then(function () {
fn.apply(void 0, args);
})
.catch(function (error) {
if (onError) {
onError(error);
}
});
};
}
/**
* Return true if the object passed in implements any of the named methods.
*/
function implementsAnyMethods(obj, methods) {
if (typeof obj !== 'object' || obj === null) {
return false;
}
for (var _i = 0, methods_1 = methods; _i < methods_1.length; _i++) {
var method = methods_1[_i];
if (method in obj && typeof obj[method] === 'function') {
return true;
}
}
return false;
}
function noop() {
// do nothing
}
//# sourceMappingURL=subscribe.js.map