Skip to content

Instantly share code, notes, and snippets.

@elie222
Created November 11, 2016 04:27
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save elie222/516a716c0eb627039ed3fd7c3fe70983 to your computer and use it in GitHub Desktop.
Save elie222/516a716c0eb627039ed3fd7c3fe70983 to your computer and use it in GitHub Desktop.
SubsManager for React Native Meteor
import Meteor from 'react-native-meteor'
import _ from 'lodash'
import Trackr from 'trackr'
import EJSON from 'ejson'
const SubsManager = function (options) {
const self = this
self.options = options || {}
// maxiumum number of subscriptions are cached
self.options.cacheLimit = self.options.cacheLimit || 10
// maximum time, subscription stay in the cache
self.options.expireIn = self.options.expireIn || 5
self._cacheMap = {}
self._cacheList = []
self._ready = false
self.dep = new Trackr.Dependency()
self.computation = self._registerComputation()
}
SubsManager.prototype.subscribe = function () {
const self = this
const args = _.toArray(arguments)
this._addSub(args)
return {
ready() {
self.dep.depend()
return self._ready
},
}
}
SubsManager.prototype._addSub = function (args) {
const self = this
const hash = EJSON.stringify(args)
// const subName = args[0]
// const paramsKey = EJSON.stringify(args.slice(1))
if (!self._cacheMap[hash]) {
const sub = {
args,
hash,
}
this._handleError(sub)
self._cacheMap[hash] = sub
self._cacheList.push(sub)
self._ready = false
// to notify the global ready()
self._notifyChanged()
// no need to interfere with the current computation
self._reRunSubs()
}
// add the current sub to the top of the list
const sub = self._cacheMap[hash]
sub.updated = (new Date()).getTime()
const index = _.indexOf(self._cacheList, sub)
self._cacheList.splice(index, 1)
self._cacheList.push(sub)
}
SubsManager.prototype._reRunSubs = function () {
const self = this
if (Trackr.currentComputation)
Trackr.afterFlush(() => self.computation.invalidate())
else
self.computation.invalidate()
}
SubsManager.prototype._notifyChanged = function () {
const self = this
if (Trackr.currentComputation)
setTimeout(() => self.dep.changed(), 0)
else
self.dep.changed()
}
SubsManager.prototype._applyCacheLimit = function () {
const self = this
const overflow = self._cacheList.length - self.options.cacheLimit
if (overflow > 0) {
const removedSubs = self._cacheList.splice(0, overflow)
_.each(removedSubs, sub => {
delete self._cacheMap[sub.hash]
})
}
}
SubsManager.prototype._applyExpirations = function () {
const self = this
const newCacheList = []
const expirationTime = (new Date()).getTime() - (self.options.expireIn * 60 * 1000)
_.each(self._cacheList, sub => {
if (sub.updated >= expirationTime)
newCacheList.push(sub)
else
delete self._cacheMap[sub.hash]
})
self._cacheList = newCacheList
}
SubsManager.prototype._registerComputation = function () {
const self = this
const computation = Trackr.autorun(() => {
self._applyExpirations()
self._applyCacheLimit()
let ready = true
_.each(self._cacheList, sub => {
sub.ready = Meteor.subscribe.apply(Meteor, sub.args).ready()
ready = ready && sub.ready
})
if (ready) {
self._ready = true
self._notifyChanged()
}
})
return computation
}
SubsManager.prototype._createIdentifier = args => {
const tmpArgs = _.map(args, value => {
if (typeof value === 'string')
return `"${value}"`
return value
})
return tmpArgs.join(', ')
}
SubsManager.prototype._handleError = function (sub) {
const args = sub.args
const lastElement = _.last(args)
sub.identifier = this._createIdentifier(args)
function errorHandlingLogic(err) {
console.log('Error invoking SubsManager.subscribe(%s): ', sub.identifier, err.reason)
// expire this sub right away.
// Then expiration machanism will take care of the sub removal
sub.updated = new Date(1)
}
if (!lastElement) {
args.push({ onError: errorHandlingLogic })
} else if (typeof lastElement === 'function') {
args.pop()
args.push({ onReady: lastElement, onError: errorHandlingLogic })
} else if (typeof lastElement.onError === 'function') {
const originalOnError = lastElement.onError
lastElement.onError = function (err) {
errorHandlingLogic(err)
originalOnError(err)
}
} else if (typeof lastElement.onReady === 'function') {
lastElement.onError = errorHandlingLogic
} else {
args.push({ onError: errorHandlingLogic })
}
}
SubsManager.prototype.reset = function () {
const self = this
const oldComputation = self.computation
self.computation = self._registerComputation()
// invalidate the new compuation and it will fire new subscriptions
self.computation.invalidate()
// after above invalidation completed, fire stop the old computation
// which then send unsub messages
// mergeBox will correct send changed data and there'll be no flicker
Trackr.afterFlush(() => oldComputation.stop())
}
SubsManager.prototype.clear = function () {
this._cacheList = []
this._cacheMap = {}
this._reRunSubs()
}
SubsManager.prototype.ready = function () {
this.dep.depend()
// if there are no items in the cacheList we are not ready yet.
if (this._cacheList.length === 0)
return false
return this._ready
}
export default SubsManager
@noris666
Copy link

Ohhh thx 4 share code, you can write how you include this code to your applications ?
To faster test ?

@elie222
Copy link
Author

elie222 commented Nov 11, 2016

This doesn't fully work after some initial testing. It may be easier to rewrite from scratch.

@radiegtya
Copy link

any update to this?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment