/**
* @copyright 2020 Tridium, Inc. All Rights Reserved.
* @author Logan Byam
*/
/* eslint-env browser */
/**
* @private
* @module baja/ord/OrdCoalescer
*/
define([
'bajaScript/comm',
'bajaScript/baja/ord/OrdTarget',
'bajaScript/baja/ord/ordUtil',
'bajaPromises' ], function (
baja,
OrdTarget,
ordUtil,
Promise) {
'use strict';
var resolveOptimized = ordUtil.resolveOptimized;
function waitInterval(delay) {
var df = Promise.deferred();
// noinspection DynamicallyGeneratedCodeJS
setTimeout(df.resolve, delay);
return df.promise();
}
/**
* This class handles requests to resolve ORDs. Unlike BajaScript itself,
* which will happily resolve the exact same ORD any number of times, this
* will introduce a small delay. Any duplicate ORD resolutions requested
* during this delay will simply be bound together into a single network call
* to resolve that one ORD.
*
* @private
* @class
* @alias module:baja/ord/OrdCoalescer
* @param {object} [params]
* @param {number} [params.delay=0] how many ms to hold outgoing ORD
* resolutions before sending them across the network
* @param {boolean} [params.provideSubstitutes] if true, the `OrdTarget`s
* resolved by the coalescer will have substitute `optimizedOrd` facets for
* future re-resolution.
* @param {boolean} [params.viewQueryCoalesce] if true, the `OrdTarget`s
* resolved by the coalescer will remove the ViewQuery when coalescing.
*/
var OrdCoalescer = function OrdCoalescer(params) {
this.$delay = (params && params.delay) || 0;
this.$pendingNoSubscribe = {};
this.$pendingSubscribe = {};
this.$provideSubstitutes = params && params.provideSubstitutes;
this.$viewQueryCoalesce = params && params.viewQueryCoalesce;
};
/**
* @param {string|baja.Ord} ord
* @param {object} [params]
* @param {baja.Subscriber} [params.subscriber] provide if you wish for the
* ORD to be subscribed
* @returns {Promise.<module:baja/ord/OrdTarget>}
*/
OrdCoalescer.prototype.resolve = function (ord, params) {
var that = this;
var subscriber = params && params.subscriber;
var df = Promise.deferred();
df.subscriber = subscriber;
var ordInfo;
if (this.$viewQueryCoalesce) {
ordInfo = ordUtil.getOrdViewQuerySplit(ord);
}
var resolution = that.$getQueuedResolution((ordInfo && ordInfo.ord) || ord, !!subscriber);
resolution.deferreds.push(df);
return this.$requestFlush()
.then(function () {
return df.promise();
})
.then(function (ordTarget) {
ordTarget = ordTarget.clone();
if (ordInfo && ordInfo.viewQuery) {
ordUtil.appendViewQueryToOrdTarget(ordTarget, ordInfo.viewQuery);
}
return ordTarget;
});
};
/**
* @private
* @param {string|baja.Ord} ord
* @param {boolean} subscribe
* @returns {module:baja/ord/OrdCoalescer~QueuedResolution}
*/
OrdCoalescer.prototype.$getQueuedResolution = function (ord, subscribe) {
var pendingSubscribe = this.$pendingSubscribe;
var pendingNoSubscribe = this.$pendingNoSubscribe;
var subscribeResolution = pendingSubscribe[ord];
var noSubscribeResolution = pendingNoSubscribe[ord];
var resolution;
if (subscribeResolution) {
resolution = subscribeResolution;
} else {
if (subscribe) {
if (noSubscribeResolution) {
resolution = pendingSubscribe[ord] = noSubscribeResolution;
delete pendingNoSubscribe[ord];
} else {
resolution = getResolution(pendingSubscribe, ord);
}
} else {
resolution = getResolution(pendingNoSubscribe, ord);
}
}
return resolution;
};
/**
* Performs all queued ORD resolutions and resolves all promises waiting on
* them. If called before the delay has expired, will wait for the delay to
* elapse first.
* @private
* @returns {Promise}
*/
OrdCoalescer.prototype.$requestFlush = function () {
var that = this;
var flushPromise = that.$flushPromise;
if (flushPromise) { return flushPromise; }
return (that.$flushPromise = waitInterval(that.$delay)
.then(function () {
return that.$flush();
}));
};
/**
* @private
* @returns {Promise}
*/
OrdCoalescer.prototype.$flush = function () {
var that = this;
var promise = Promise.all([
that.$batchResolvePending(that.$pendingSubscribe, true),
that.$batchResolvePending(that.$pendingNoSubscribe)
]);
that.$pendingSubscribe = {};
that.$pendingNoSubscribe = {};
delete that.$flushPromise;
return promise;
};
/**
* @private
* @param {Object<string, module:baja/ord/OrdCoalescer~QueuedResolution>} pending
* @param {boolean} subscribe
* @returns {Promise}
*/
OrdCoalescer.prototype.$batchResolvePending = function (pending, subscribe) {
var batchSubscriber = subscribe ? new baja.Subscriber() : undefined;
var ords = Object.keys(pending);
if (!ords.length) {
return Promise.resolve();
}
return this.$batchResolve({ ords: ords, subscriber: batchSubscriber })
.then(function (results) {
return Promise.all(results.map(function (result, i) {
var ord = ords[i];
var isResolved = !(result instanceof Error);
var deferreds = pending[ord].deferreds;
return Promise.all(deferreds.map(function (df) {
var resolve = df.resolve;
var reject = df.reject;
var subscriber = df.subscriber;
if (!isResolved) {
return reject(result);
}
var ordTarget = result;
var component = ordTarget.getComponent();
if (subscriber && isSubscribable(component)) {
return subscriber.subscribe(component)
.then(function () { resolve(ordTarget); });
} else {
resolve(ordTarget);
}
}));
}));
})
.then(function () {
return batchSubscriber && batchSubscriber.unsubscribeAll();
});
};
/**
* @private
* @param {object} params
* @param {Array.<string|baja.Ord>} params.ords
* @param {baja.Subscriber} [params.subscriber]
* @returns {Promise.<Array.<module:baja/ord/OrdTarget|Error>>} array of
* results of each ord resolution - either an OrdTarget or Error instance
*/
OrdCoalescer.prototype.$batchResolve = function (params) {
var resolvePromise;
if (this.$provideSubstitutes) {
resolvePromise = resolveOptimized(params, {});
} else {
var br = new baja.BatchResolve(params.ords);
resolvePromise = br.resolve({ subscriber: params.subscriber })
.catch(function (ignore) {})
.then(function () { return br; });
}
return resolvePromise
.then(function (br) {
return range(br.size()).map(function (i) {
return br.isResolved(i) ? br.getTarget(i) : br.getFail(i);
});
});
};
/**
* Resolution of a single ORD, that may satisfy any number of `resolve()`
* calls for that particular ORD.
*
* @typedef module:baja/ord/OrdCoalescer~QueuedResolution
* @property {Array.<module:baja/ord/OrdCoalescer~Deferred>} deferreds deferred
* promises waiting on this one ORD to be resolved
*/
/**
* @typedef module:baja/ord/OrdCoalescer~Deferred
* @property {function} resolve function to resolve the caller's promise
* @property {function} reject function to reject the caller's promise
* @property {baja.Subscriber} subscriber provided if the caller wishes to
* subscribe the ORD
*/
function getResolution(pending, ord) {
return pending[ord] || (pending[ord] = { deferreds: [] });
}
function isSubscribable(val) {
return baja.hasType(val, 'baja:Component') && val.isMounted();
}
function range(length) {
var arr = new Array(length);
for (var i = 0; i < length; ++i) { arr[i] = i; }
return arr;
}
return OrdCoalescer;
});