From 5b3ae75fb216d898b6b56b3a18a1f0981263c865 Mon Sep 17 00:00:00 2001 From: Jeremy Danyow Date: Sun, 20 Dec 2015 22:57:49 -0500 Subject: [PATCH] fix(subscriberCollection): handle cascading calls Fixes #252, Fixes jdanyow/aurelia-computed#12 Handles scenarios where observers have subscriptions that overflow into the 'rest' buckets and calling one observer's subscribers causes another observer's subscribers to be called --- src/subscriber-collection.js | 54 +++++++++++++++++++++--------- test/subscriber-collection.spec.js | 46 +++++++++++++++++++++++++ 2 files changed, 85 insertions(+), 15 deletions(-) create mode 100644 test/subscriber-collection.spec.js diff --git a/src/subscriber-collection.js b/src/subscriber-collection.js index 463dd1a8..968e6d66 100644 --- a/src/subscriber-collection.js +++ b/src/subscriber-collection.js @@ -53,8 +53,9 @@ function removeSubscriber(context, callable) { return true; } -let tempContextsRest = [null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null]; -let tempCallablesRest = [null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null]; +let arrayPool1 = []; +let arrayPool2 = []; +let poolUtilization = []; function callSubscribers(newValue, oldValue) { let context0 = this._context0; @@ -63,12 +64,32 @@ function callSubscribers(newValue, oldValue) { let callable1 = this._callable1; let context2 = this._context2; let callable2 = this._callable2; - let length = !this._contextsRest ? 0 : this._contextsRest.length; - let i = length; + let length = this._contextsRest ? this._contextsRest.length : 0; + let contextsRest; + let callablesRest; + let poolIndex; + let i; if (length) { + // grab temp arrays from the pool. + poolIndex = poolUtilization.length; + while (poolIndex-- && poolUtilization[poolIndex]) { } + if (poolIndex < 0) { + poolIndex = poolUtilization.length; + contextsRest = []; + callablesRest = []; + poolUtilization.push(true); + arrayPool1.push(contextsRest); + arrayPool2.push(callablesRest); + } else { + poolUtilization[poolIndex] = true; + contextsRest = arrayPool1[poolIndex]; + callablesRest = arrayPool2[poolIndex]; + } + // copy the contents of the "rest" arrays. + i = length; while(i--) { - tempContextsRest[i] = this._contextsRest[i]; - tempCallablesRest[i] = this._callablesRest[i]; + contextsRest[i] = this._contextsRest[i]; + callablesRest[i] = this._callablesRest[i]; } } @@ -93,16 +114,19 @@ function callSubscribers(newValue, oldValue) { context2(newValue, oldValue); } } - for (i = 0; i < length; i++) { - let callable = tempCallablesRest[i]; - let context = tempContextsRest[i] - if (callable) { - callable.call(context, newValue, oldValue); - } else { - context(newValue, oldValue); + if (length) { + for (i = 0; i < length; i++) { + let callable = callablesRest[i]; + let context = contextsRest[i] + if (callable) { + callable.call(context, newValue, oldValue); + } else { + context(newValue, oldValue); + } + contextsRest[i] = null; + callablesRest[i] = null; } - tempContextsRest[i] = null; - tempCallablesRest[i] = null; + poolUtilization[poolIndex] = false; } } diff --git a/test/subscriber-collection.spec.js b/test/subscriber-collection.spec.js new file mode 100644 index 00000000..01b16b90 --- /dev/null +++ b/test/subscriber-collection.spec.js @@ -0,0 +1,46 @@ +import {subscriberCollection} from '../src/subscriber-collection'; + +@subscriberCollection() +class Test { } + +describe('subscriberCollection', () => { + it('calls subscribers', () => { + let observer = new Test(); + let observer2 = new Test(); + + let callable1 = { call: jasmine.createSpy('call') }; + observer.addSubscriber('1', callable1); + let callable2 = { call: jasmine.createSpy('call') }; + observer.addSubscriber('2', callable2); + let callable3 = { call: jasmine.createSpy('call') }; + observer.addSubscriber('3', callable3); + let callable4 = { call: jasmine.createSpy('call').and.callFake(() => observer2.callSubscribers('new value2', 'old value2')) }; + observer.addSubscriber('4', callable4); + let callable5 = { call: jasmine.createSpy('call') }; + observer.addSubscriber('5', callable5); + + let callable6 = { call: jasmine.createSpy('call') }; + observer2.addSubscriber('6', callable6); + let callable7 = { call: jasmine.createSpy('call') }; + observer2.addSubscriber('7', callable7); + let callable8 = { call: jasmine.createSpy('call') }; + observer2.addSubscriber('8', callable8); + let callable9 = { call: jasmine.createSpy('call') }; + observer2.addSubscriber('9', callable9); + let callable10 = { call: jasmine.createSpy('call') }; + observer2.addSubscriber('10', callable10); + + observer.callSubscribers('new value', 'old value'); + + expect(callable1.call).toHaveBeenCalledWith('1', 'new value', 'old value'); + expect(callable2.call).toHaveBeenCalledWith('2', 'new value', 'old value'); + expect(callable3.call).toHaveBeenCalledWith('3', 'new value', 'old value'); + expect(callable4.call).toHaveBeenCalledWith('4', 'new value', 'old value'); + expect(callable5.call).toHaveBeenCalledWith('5', 'new value', 'old value'); + expect(callable6.call).toHaveBeenCalledWith('6', 'new value2', 'old value2'); + expect(callable7.call).toHaveBeenCalledWith('7', 'new value2', 'old value2'); + expect(callable8.call).toHaveBeenCalledWith('8', 'new value2', 'old value2'); + expect(callable9.call).toHaveBeenCalledWith('9', 'new value2', 'old value2'); + expect(callable10.call).toHaveBeenCalledWith('10', 'new value2', 'old value2'); + }); +});