Skip to content

Commit

Permalink
Optimize createListenerCollection (#1523)
Browse files Browse the repository at this point in the history
  • Loading branch information
wurstbonbon committed Feb 18, 2020
1 parent 3eb5271 commit e649fb6
Show file tree
Hide file tree
Showing 2 changed files with 96 additions and 17 deletions.
56 changes: 39 additions & 17 deletions src/utils/Subscription.js
Expand Up @@ -4,46 +4,68 @@ import { getBatch } from './batch'
// well as nesting subscriptions of descendant components, so that we can ensure the
// ancestor components re-render before descendants

const CLEARED = null
const nullListeners = { notify() {} }

function createListenerCollection() {
const batch = getBatch()
// the current/next pattern is copied from redux's createStore code.
// TODO: refactor+expose that code to be reusable here?
let current = []
let next = []
let first = null
let last = null

return {
clear() {
next = CLEARED
current = CLEARED
first = null
last = null
},

notify() {
const listeners = (current = next)
batch(() => {
for (let i = 0; i < listeners.length; i++) {
listeners[i]()
let listener = first
while (listener) {
listener.callback()
listener = listener.next
}
})
},

get() {
return next
let listeners = []
let listener = first
while (listener) {
listeners.push(listener)
listener = listener.next
}
return listeners
},

subscribe(listener) {
subscribe(callback) {
let isSubscribed = true
if (next === current) next = current.slice()
next.push(listener)

let listener = (last = {
callback,
next: null,
prev: last
})

if (listener.prev) {
listener.prev.next = listener
} else {
first = listener
}

return function unsubscribe() {
if (!isSubscribed || current === CLEARED) return
if (!isSubscribed || first === null) return
isSubscribed = false

if (next === current) next = current.slice()
next.splice(next.indexOf(listener), 1)
if (listener.next) {
listener.next.prev = listener.prev
} else {
last = listener.prev
}
if (listener.prev) {
listener.prev.next = listener.next
} else {
first = listener.next
}
}
}
}
Expand Down
57 changes: 57 additions & 0 deletions test/utils/Subscription.spec.js
@@ -0,0 +1,57 @@
import Subscription from '../../src/utils/Subscription'

describe('Subscription', () => {
let notifications
let store
let parent

beforeEach(() => {
notifications = []
store = { subscribe: () => jest.fn() }

parent = new Subscription(store)
parent.onStateChange = () => {}
parent.trySubscribe()
})

function subscribeChild(name) {
const child = new Subscription(store, parent)
child.onStateChange = () => notifications.push(name)
child.trySubscribe()
return child
}

it('listeners are notified in order', () => {
subscribeChild('child1')
subscribeChild('child2')
subscribeChild('child3')
subscribeChild('child4')

parent.notifyNestedSubs()

expect(notifications).toEqual(['child1', 'child2', 'child3', 'child4'])
})

it('listeners can be unsubscribed', () => {
const child1 = subscribeChild('child1')
const child2 = subscribeChild('child2')
const child3 = subscribeChild('child3')

child2.tryUnsubscribe()
parent.notifyNestedSubs()

expect(notifications).toEqual(['child1', 'child3'])
notifications.length = 0

child1.tryUnsubscribe()
parent.notifyNestedSubs()

expect(notifications).toEqual(['child3'])
notifications.length = 0

child3.tryUnsubscribe()
parent.notifyNestedSubs()

expect(notifications).toEqual([])
})
})

0 comments on commit e649fb6

Please sign in to comment.