Commit 3b37289a by benjaminapetersen

Add opts.skipDigest for DataService.watch

- provides mechanism to keep WebSockets from spamming the $digest loop
- flips invokeApply passed to the internal $timeout calls,
  stopping a $digest loop & avoiding a render.
- DataService._watchOpOnMessage also honors the skipDigest flag,
  which is plumbed down through .watch() -> .startListOp -> ._listOpComplete -> ._startWatchOp -> ._watchOpOnMessage

example usage:

DataService.watch(resource, context, (data) => {
  // optionally call $applyAsync here in controlling code
  // to manually run a $digest
  $scope.$applyAsync(() => {
   // do stuff with data here
  });
}, { skipDigest: true });

// or Debounce
DataService.watch(resource, context, _.debounce(() => {
 // $apply here, inside debounce
}, 400), { skipDigest: true });
parent 27a8f85a
...@@ -1736,6 +1736,9 @@ DataService.prototype.createStream = function(resource, name, context, opts, isR ...@@ -1736,6 +1736,9 @@ DataService.prototype.createStream = function(resource, name, context, opts, isR
// pollInterval: in milliseconds, how long to wait between polling the server // pollInterval: in milliseconds, how long to wait between polling the server
// only applies if poll=true. Default is 5000. // only applies if poll=true. Default is 5000.
// http: similar to .get, etc. at this point, only used to pass http.params for filtering // http: similar to .get, etc. at this point, only used to pass http.params for filtering
// skipDigest: will skip the $apply & avoid triggering a digest loop
// if set to `true`. Is intentionally the inverse of the invokeApply
// arg passed to $timeout (due to default values).
// errorNotification: will popup an error notification if the API request fails (default true) // errorNotification: will popup an error notification if the API request fails (default true)
// returns handle to the watch, needed to unwatch e.g. // returns handle to the watch, needed to unwatch e.g.
// var handle = DataService.watch(resource,context,callback[,opts]) // var handle = DataService.watch(resource,context,callback[,opts])
...@@ -1743,6 +1746,7 @@ DataService.prototype.createStream = function(resource, name, context, opts, isR ...@@ -1743,6 +1746,7 @@ DataService.prototype.createStream = function(resource, name, context, opts, isR
DataService.prototype.watch = function(resource, context, callback, opts) { DataService.prototype.watch = function(resource, context, callback, opts) {
resource = APIService.toResourceGroupVersion(resource); resource = APIService.toResourceGroupVersion(resource);
opts = opts || {}; opts = opts || {};
var invokeApply = !opts.skipDigest;
var key = this._uniqueKey(resource, null, context, _.get(opts, 'http.params')); var key = this._uniqueKey(resource, null, context, _.get(opts, 'http.params'));
if (callback) { if (callback) {
// If we were given a callback, add it // If we were given a callback, add it
...@@ -1770,7 +1774,7 @@ DataService.prototype.createStream = function(resource, name, context, opts, isR ...@@ -1770,7 +1774,7 @@ DataService.prototype.createStream = function(resource, name, context, opts, isR
if (callback) { if (callback) {
$timeout(function() { $timeout(function() {
callback(self._data(key)); callback(self._data(key));
}, 0); }, 0, invokeApply);
} }
} }
else { else {
...@@ -1782,7 +1786,7 @@ DataService.prototype.createStream = function(resource, name, context, opts, isR ...@@ -1782,7 +1786,7 @@ DataService.prototype.createStream = function(resource, name, context, opts, isR
if (resourceVersion === self._resourceVersion(key)) { if (resourceVersion === self._resourceVersion(key)) {
callback(self._data(key)); // but just in case, still pull from the current data map callback(self._data(key)); // but just in case, still pull from the current data map
} }
}, 0); }, 0, invokeApply);
} }
} }
if (!this._listInFlight(key)) { if (!this._listInFlight(key)) {
...@@ -2245,6 +2249,8 @@ DataService.prototype.createStream = function(resource, name, context, opts, isR ...@@ -2245,6 +2249,8 @@ DataService.prototype.createStream = function(resource, name, context, opts, isR
DataService.prototype._watchOpOnMessage = function(resource, context, opts, event) { DataService.prototype._watchOpOnMessage = function(resource, context, opts, event) {
var key = this._uniqueKey(resource, null, context, _.get(opts, 'http.params')); var key = this._uniqueKey(resource, null, context, _.get(opts, 'http.params'));
opts = opts || {};
var invokeApply = !opts.skipDigest;
try { try {
var eventData = $.parseJSON(event.data); var eventData = $.parseJSON(event.data);
...@@ -2274,7 +2280,7 @@ DataService.prototype.createStream = function(resource, name, context, opts, isR ...@@ -2274,7 +2280,7 @@ DataService.prototype.createStream = function(resource, name, context, opts, isR
// without timeout this is triggering a repeated digest loop // without timeout this is triggering a repeated digest loop
$timeout(function() { $timeout(function() {
self._watchCallbacks(key).fire(self._data(key), eventData.type, eventData.object); self._watchCallbacks(key).fire(self._data(key), eventData.type, eventData.object);
}, 0); }, 0, invokeApply);
} }
catch (e) { catch (e) {
// TODO: surface in the UI? // TODO: surface in the UI?
......
...@@ -3502,6 +3502,9 @@ DataService.prototype.createStream = function(resource, name, context, opts, isR ...@@ -3502,6 +3502,9 @@ DataService.prototype.createStream = function(resource, name, context, opts, isR
// pollInterval: in milliseconds, how long to wait between polling the server // pollInterval: in milliseconds, how long to wait between polling the server
// only applies if poll=true. Default is 5000. // only applies if poll=true. Default is 5000.
// http: similar to .get, etc. at this point, only used to pass http.params for filtering // http: similar to .get, etc. at this point, only used to pass http.params for filtering
// skipDigest: will skip the $apply & avoid triggering a digest loop
// if set to `true`. Is intentionally the inverse of the invokeApply
// arg passed to $timeout (due to default values).
// errorNotification: will popup an error notification if the API request fails (default true) // errorNotification: will popup an error notification if the API request fails (default true)
// returns handle to the watch, needed to unwatch e.g. // returns handle to the watch, needed to unwatch e.g.
// var handle = DataService.watch(resource,context,callback[,opts]) // var handle = DataService.watch(resource,context,callback[,opts])
...@@ -3509,6 +3512,7 @@ DataService.prototype.createStream = function(resource, name, context, opts, isR ...@@ -3509,6 +3512,7 @@ DataService.prototype.createStream = function(resource, name, context, opts, isR
DataService.prototype.watch = function(resource, context, callback, opts) { DataService.prototype.watch = function(resource, context, callback, opts) {
resource = APIService.toResourceGroupVersion(resource); resource = APIService.toResourceGroupVersion(resource);
opts = opts || {}; opts = opts || {};
var invokeApply = !opts.skipDigest;
var key = this._uniqueKey(resource, null, context, _.get(opts, 'http.params')); var key = this._uniqueKey(resource, null, context, _.get(opts, 'http.params'));
if (callback) { if (callback) {
// If we were given a callback, add it // If we were given a callback, add it
...@@ -3536,7 +3540,7 @@ DataService.prototype.createStream = function(resource, name, context, opts, isR ...@@ -3536,7 +3540,7 @@ DataService.prototype.createStream = function(resource, name, context, opts, isR
if (callback) { if (callback) {
$timeout(function() { $timeout(function() {
callback(self._data(key)); callback(self._data(key));
}, 0); }, 0, invokeApply);
} }
} }
else { else {
...@@ -3548,7 +3552,7 @@ DataService.prototype.createStream = function(resource, name, context, opts, isR ...@@ -3548,7 +3552,7 @@ DataService.prototype.createStream = function(resource, name, context, opts, isR
if (resourceVersion === self._resourceVersion(key)) { if (resourceVersion === self._resourceVersion(key)) {
callback(self._data(key)); // but just in case, still pull from the current data map callback(self._data(key)); // but just in case, still pull from the current data map
} }
}, 0); }, 0, invokeApply);
} }
} }
if (!this._listInFlight(key)) { if (!this._listInFlight(key)) {
...@@ -4011,6 +4015,8 @@ DataService.prototype.createStream = function(resource, name, context, opts, isR ...@@ -4011,6 +4015,8 @@ DataService.prototype.createStream = function(resource, name, context, opts, isR
DataService.prototype._watchOpOnMessage = function(resource, context, opts, event) { DataService.prototype._watchOpOnMessage = function(resource, context, opts, event) {
var key = this._uniqueKey(resource, null, context, _.get(opts, 'http.params')); var key = this._uniqueKey(resource, null, context, _.get(opts, 'http.params'));
opts = opts || {};
var invokeApply = !opts.skipDigest;
try { try {
var eventData = $.parseJSON(event.data); var eventData = $.parseJSON(event.data);
...@@ -4040,7 +4046,7 @@ DataService.prototype.createStream = function(resource, name, context, opts, isR ...@@ -4040,7 +4046,7 @@ DataService.prototype.createStream = function(resource, name, context, opts, isR
// without timeout this is triggering a repeated digest loop // without timeout this is triggering a repeated digest loop
$timeout(function() { $timeout(function() {
self._watchCallbacks(key).fire(self._data(key), eventData.type, eventData.object); self._watchCallbacks(key).fire(self._data(key), eventData.type, eventData.object);
}, 0); }, 0, invokeApply);
} }
catch (e) { catch (e) {
// TODO: surface in the UI? // TODO: surface in the UI?
......
...@@ -1471,7 +1471,7 @@ ws.close(); ...@@ -1471,7 +1471,7 @@ ws.close();
}; };
}, DataService.prototype.watch = function(resource, context, callback, opts) { }, DataService.prototype.watch = function(resource, context, callback, opts) {
resource = APIService.toResourceGroupVersion(resource), opts = opts || {}; resource = APIService.toResourceGroupVersion(resource), opts = opts || {};
var key = this._uniqueKey(resource, null, context, _.get(opts, "http.params")); var invokeApply = !opts.skipDigest, key = this._uniqueKey(resource, null, context, _.get(opts, "http.params"));
if (callback) this._watchCallbacks(key).add(callback); else if (!this._watchCallbacks(key).has()) return {}; if (callback) this._watchCallbacks(key).add(callback); else if (!this._watchCallbacks(key).has()) return {};
var existingWatchOpts = this._watchOptions(key); var existingWatchOpts = this._watchOptions(key);
if (existingWatchOpts) { if (existingWatchOpts) {
...@@ -1480,12 +1480,12 @@ if (!!existingWatchOpts.poll != !!opts.poll) throw "A watch already exists for " ...@@ -1480,12 +1480,12 @@ if (!!existingWatchOpts.poll != !!opts.poll) throw "A watch already exists for "
var self = this; var self = this;
if (this._isCached(key)) callback && $timeout(function() { if (this._isCached(key)) callback && $timeout(function() {
callback(self._data(key)); callback(self._data(key));
}, 0); else { }, 0, invokeApply); else {
if (callback) { if (callback) {
var resourceVersion = this._resourceVersion(key); var resourceVersion = this._resourceVersion(key);
this._data(key) && $timeout(function() { this._data(key) && $timeout(function() {
resourceVersion === self._resourceVersion(key) && callback(self._data(key)); resourceVersion === self._resourceVersion(key) && callback(self._data(key));
}, 0); }, 0, invokeApply);
} }
this._listInFlight(key) || this._startListOp(resource, context, opts); this._listInFlight(key) || this._startListOp(resource, context, opts);
} }
...@@ -1645,6 +1645,8 @@ var key = this._uniqueKey(resource, null, context, _.get(opts, "http.params")); ...@@ -1645,6 +1645,8 @@ var key = this._uniqueKey(resource, null, context, _.get(opts, "http.params"));
this._addWebsocketEvent(key, "open"); this._addWebsocketEvent(key, "open");
}, DataService.prototype._watchOpOnMessage = function(resource, context, opts, event) { }, DataService.prototype._watchOpOnMessage = function(resource, context, opts, event) {
var key = this._uniqueKey(resource, null, context, _.get(opts, "http.params")); var key = this._uniqueKey(resource, null, context, _.get(opts, "http.params"));
opts = opts || {};
var invokeApply = !opts.skipDigest;
try { try {
var eventData = $.parseJSON(event.data); var eventData = $.parseJSON(event.data);
if ("ERROR" == eventData.type) return Logger.log("Watch window expired for resource/context", resource, context), void (event.target && (event.target.shouldRelist = !0)); if ("ERROR" == eventData.type) return Logger.log("Watch window expired for resource/context", resource, context), void (event.target && (event.target.shouldRelist = !0));
...@@ -1652,7 +1654,7 @@ if ("ERROR" == eventData.type) return Logger.log("Watch window expired for resou ...@@ -1652,7 +1654,7 @@ if ("ERROR" == eventData.type) return Logger.log("Watch window expired for resou
var self = this; var self = this;
$timeout(function() { $timeout(function() {
self._watchCallbacks(key).fire(self._data(key), eventData.type, eventData.object); self._watchCallbacks(key).fire(self._data(key), eventData.type, eventData.object);
}, 0); }, 0, invokeApply);
} catch (e) { } catch (e) {
Logger.error("Error processing message", resource, event.data); Logger.error("Error processing message", resource, event.data);
} }
......
...@@ -602,6 +602,9 @@ DataService.prototype.createStream = function(resource, name, context, opts, isR ...@@ -602,6 +602,9 @@ DataService.prototype.createStream = function(resource, name, context, opts, isR
// pollInterval: in milliseconds, how long to wait between polling the server // pollInterval: in milliseconds, how long to wait between polling the server
// only applies if poll=true. Default is 5000. // only applies if poll=true. Default is 5000.
// http: similar to .get, etc. at this point, only used to pass http.params for filtering // http: similar to .get, etc. at this point, only used to pass http.params for filtering
// skipDigest: will skip the $apply & avoid triggering a digest loop
// if set to `true`. Is intentionally the inverse of the invokeApply
// arg passed to $timeout (due to default values).
// errorNotification: will popup an error notification if the API request fails (default true) // errorNotification: will popup an error notification if the API request fails (default true)
// returns handle to the watch, needed to unwatch e.g. // returns handle to the watch, needed to unwatch e.g.
// var handle = DataService.watch(resource,context,callback[,opts]) // var handle = DataService.watch(resource,context,callback[,opts])
...@@ -609,6 +612,7 @@ DataService.prototype.createStream = function(resource, name, context, opts, isR ...@@ -609,6 +612,7 @@ DataService.prototype.createStream = function(resource, name, context, opts, isR
DataService.prototype.watch = function(resource, context, callback, opts) { DataService.prototype.watch = function(resource, context, callback, opts) {
resource = APIService.toResourceGroupVersion(resource); resource = APIService.toResourceGroupVersion(resource);
opts = opts || {}; opts = opts || {};
var invokeApply = !opts.skipDigest;
var key = this._uniqueKey(resource, null, context, _.get(opts, 'http.params')); var key = this._uniqueKey(resource, null, context, _.get(opts, 'http.params'));
if (callback) { if (callback) {
// If we were given a callback, add it // If we were given a callback, add it
...@@ -636,7 +640,7 @@ DataService.prototype.createStream = function(resource, name, context, opts, isR ...@@ -636,7 +640,7 @@ DataService.prototype.createStream = function(resource, name, context, opts, isR
if (callback) { if (callback) {
$timeout(function() { $timeout(function() {
callback(self._data(key)); callback(self._data(key));
}, 0); }, 0, invokeApply);
} }
} }
else { else {
...@@ -648,7 +652,7 @@ DataService.prototype.createStream = function(resource, name, context, opts, isR ...@@ -648,7 +652,7 @@ DataService.prototype.createStream = function(resource, name, context, opts, isR
if (resourceVersion === self._resourceVersion(key)) { if (resourceVersion === self._resourceVersion(key)) {
callback(self._data(key)); // but just in case, still pull from the current data map callback(self._data(key)); // but just in case, still pull from the current data map
} }
}, 0); }, 0, invokeApply);
} }
} }
if (!this._listInFlight(key)) { if (!this._listInFlight(key)) {
...@@ -1111,6 +1115,8 @@ DataService.prototype.createStream = function(resource, name, context, opts, isR ...@@ -1111,6 +1115,8 @@ DataService.prototype.createStream = function(resource, name, context, opts, isR
DataService.prototype._watchOpOnMessage = function(resource, context, opts, event) { DataService.prototype._watchOpOnMessage = function(resource, context, opts, event) {
var key = this._uniqueKey(resource, null, context, _.get(opts, 'http.params')); var key = this._uniqueKey(resource, null, context, _.get(opts, 'http.params'));
opts = opts || {};
var invokeApply = !opts.skipDigest;
try { try {
var eventData = $.parseJSON(event.data); var eventData = $.parseJSON(event.data);
...@@ -1140,7 +1146,7 @@ DataService.prototype.createStream = function(resource, name, context, opts, isR ...@@ -1140,7 +1146,7 @@ DataService.prototype.createStream = function(resource, name, context, opts, isR
// without timeout this is triggering a repeated digest loop // without timeout this is triggering a repeated digest loop
$timeout(function() { $timeout(function() {
self._watchCallbacks(key).fire(self._data(key), eventData.type, eventData.object); self._watchCallbacks(key).fire(self._data(key), eventData.type, eventData.object);
}, 0); }, 0, invokeApply);
} }
catch (e) { catch (e) {
// TODO: surface in the UI? // TODO: surface in the UI?
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment