/
subscribable.js
167 lines (138 loc) · 4.65 KB
/
subscribable.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
/* eslint no-cond-assign: 0 */
import {
setPrototypeOfOrExtend, arrayRemoveItem, objectForEach,
canSetPrototype, setPrototypeOf, options
} from 'tko.utils'
import { applyExtenders } from './extenders.js'
import * as dependencyDetection from './dependencyDetection.js'
export function subscription (target, callback, disposeCallback) {
this._target = target
this.callback = callback
this.disposeCallback = disposeCallback
this.isDisposed = false
}
subscription.prototype.dispose = function () {
this.isDisposed = true
this.disposeCallback()
}
export function subscribable () {
setPrototypeOfOrExtend(this, ko_subscribable_fn)
ko_subscribable_fn.init(this)
}
export var defaultEvent = 'change'
var ko_subscribable_fn = {
init (instance) {
instance._subscriptions = { change: [] }
instance._versionNumber = 1
},
subscribe (callback, callbackTarget, event) {
var self = this
event = event || defaultEvent
var boundCallback = callbackTarget ? callback.bind(callbackTarget) : callback
var subscriptionInstance = new subscription(self, boundCallback, function () {
arrayRemoveItem(self._subscriptions[event], subscriptionInstance)
if (self.afterSubscriptionRemove) {
self.afterSubscriptionRemove(event)
}
})
if (self.beforeSubscriptionAdd) {
self.beforeSubscriptionAdd(event)
}
if (!self._subscriptions[event]) {
self._subscriptions[event] = []
}
self._subscriptions[event].push(subscriptionInstance)
return subscriptionInstance
},
notifySubscribers (valueToNotify, event) {
event = event || defaultEvent
if (event === defaultEvent) {
this.updateVersion()
}
if (this.hasSubscriptionsForEvent(event)) {
const subs = event === defaultEvent && this._changeSubscriptions
|| [...this._subscriptions[event]]
try {
dependencyDetection.begin() // Begin suppressing dependency detection (by setting the top frame to undefined)
for (let i = 0, subscriptionInstance; subscriptionInstance = subs[i]; ++i) {
// In case a subscription was disposed during the arrayForEach cycle, check
// for isDisposed on each subscription before invoking its callback
if (!subscriptionInstance.isDisposed) {
subscriptionInstance.callback(valueToNotify)
}
}
} finally {
dependencyDetection.end() // End suppressing dependency detection
}
}
},
getVersion () {
return this._versionNumber
},
hasChanged (versionToCheck) {
return this.getVersion() !== versionToCheck
},
updateVersion () {
++this._versionNumber
},
hasSubscriptionsForEvent (event) {
return this._subscriptions[event] && this._subscriptions[event].length
},
getSubscriptionsCount (event) {
if (event) {
return this._subscriptions[event] && this._subscriptions[event].length || 0
} else {
var total = 0
objectForEach(this._subscriptions, function (eventName, subscriptions) {
if (eventName !== 'dirty') {
total += subscriptions.length
}
})
return total
}
},
isDifferent (oldValue, newValue) {
return !this.equalityComparer ||
!this.equalityComparer(oldValue, newValue)
},
once (cb) {
const subs = this.subscribe((nv) => {
subs.dispose()
cb(nv)
})
},
when (test, returnValue) {
const current = this.peek()
const givenRv = arguments.length > 1
const testFn = typeof test === 'function' ? test : v => v === test
if (testFn(current)) {
return options.Promise.resolve(givenRv ? returnValue : current)
}
return new options.Promise((resolve, reject) => {
const subs = this.subscribe(newValue => {
if (testFn(newValue)) {
subs.dispose()
resolve(givenRv ? returnValue : newValue)
}
})
})
},
yet (test, ...args) {
const testFn = typeof test === 'function' ? test : v => v === test
const negated = v => !testFn(v)
return this.when(negated, ...args)
},
next () { return new Promise(resolve => this.once(resolve)) },
toString () { return '[object Object]' },
extend: applyExtenders
}
// For browsers that support proto assignment, we overwrite the prototype of each
// observable instance. Since observables are functions, we need Function.prototype
// to still be in the prototype chain.
if (canSetPrototype) {
setPrototypeOf(ko_subscribable_fn, Function.prototype)
}
subscribable.fn = ko_subscribable_fn
export function isSubscribable (instance) {
return instance != null && typeof instance.subscribe === 'function' && typeof instance.notifySubscribers === 'function'
}