Last active
October 28, 2018 00:22
-
-
Save machty/906a95d6625bdc66acdb238889366832 to your computer and use it in GitHub Desktop.
New Twiddle
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import Ember from 'ember'; | |
import { subscription } from "../subscription"; | |
import { bind } from '@ember/runloop'; | |
import { task, timeout } from 'ember-concurrency'; | |
export default Ember.Controller.extend({ | |
chatRooms:[ "EmberChat", "ReactChat", "VueChat", "AngularChat"], | |
chatRoomName: null, | |
// a subscription is like a Computed Property, except: | |
// it's run automatically when the host object is created | |
chatRoomSubscription: subscription('chatRoomName', function(chatRoomName) { | |
if (!chatRoomName) { | |
return; // don't subscribe | |
} | |
let intervalId = setInterval(bind(() => { | |
this.log(`${chatRoomName}: chatRoomSubscription says: ${Math.round(Math.random() * 5000)}`); | |
}), 500); | |
// return a disposer function for when we unsubscribe. | |
// This co-locates subscription creation with teardown so that | |
// creation doesn't need to be in a separate lifecycle hook (nor | |
// do we need to stash any intermediate state, i.e. this._intervalIdToCleanup) | |
return () => clearInterval(intervalId); | |
}), | |
chatRoomDidChange: subscription('chatRoomName', function(chatRoomName) { | |
// this could be moved to `chatRoomSubscription`; | |
// this just demonstrates that a subscription with | |
// no disposer function is basically an observer | |
if (chatRoomName) { | |
this.log(`Chat room changed to ${chatRoomName}`) | |
} | |
}), | |
// Here's another subscription to the same value, | |
// only this time it delegates to an ember-concurrency task; | |
// This might seem strange, but an ember-concurrency TaskInstance | |
// fits into this API nicely because the concept of self-cleanup | |
// and teardown logic is built right into its Task API and its | |
// use of generator functions. | |
chatRoomSubscriptionViaTask: subscription('chatRoomName', function(chatRoomName) { | |
return this.chatTask.perform(chatRoomName); | |
}), | |
chatTask: task(function * (chatRoomName) { | |
if (!chatRoomName) { | |
return; // don't subscribe | |
} | |
while(true) { | |
// this infinite loop stops when the subscription and hence this | |
// TaskInstance is cancelled. | |
yield timeout(300 + 500 * Math.random()) | |
this.log(`${chatRoomName}: chatTask says: ${Math.round(Math.random() * 5000)}`); | |
} | |
}), | |
messages: [], | |
log(message) { | |
let messages = this.messages.slice(0, 20); | |
messages.unshift(message); | |
this.set('messages', messages); | |
}, | |
actions: { | |
changeChatRoomName(name, e) { | |
e.preventDefault(); | |
this.set('chatRoomName', name); | |
} | |
}, | |
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import ComputedProperty from '@ember/object/computed'; | |
import { addListener } from '@ember/object/events'; | |
import { addObserver } from '@ember/object/observers'; | |
class SubscriptionManager extends ComputedProperty { | |
constructor(subscriptionConstructor, dependentKeys) { | |
function getter(propName) { | |
let hostObject = this; | |
let currentSubscriptionKey = `_${propName}`; | |
let currentSubscription = hostObject[currentSubscriptionKey]; | |
let newValues = dependentKeys.map(key => hostObject[key]); | |
let newKey = newValues.join(newValues.join(';')); | |
let currentKey = currentSubscription && currentSubscription.key || "perfect is the enemy of perfectly adequate"; | |
if (newKey === currentKey) { | |
return currentSubscription.value; | |
} else if (newKey !== currentKey) { | |
if (currentSubscription && currentSubscription.dispose) { currentSubscription.dispose(); } | |
let value = subscriptionConstructor.apply(hostObject, newValues); | |
let newSubscription; | |
// it should be possible to return | |
if (value) { | |
if (typeof value === 'function') { | |
newSubscription = { dispose: value, value: true }; | |
} else if (typeof value.dispose === 'function') { | |
newSubscription = { dispose: () => value.dispose(), value }; | |
} else if (typeof value.cancel === 'function') { | |
// ember-concurrency hacks: a TaskInstance can be considered a subscription | |
newSubscription = { dispose: () => value.cancel(), value }; | |
} else { | |
// TODO: should we complain about a leak? i.e. a subscription with no teardown? | |
newSubscription = {} | |
} | |
} else { | |
// nothing returned, no new subscription. | |
newSubscription = {} | |
} | |
newSubscription.key = newKey; | |
if (!currentSubscription) { | |
_cleanupOnDestroy(hostObject, () => { | |
let v = hostObject[currentSubscriptionKey]; | |
if (v && v.dispose) { | |
v.dispose(); | |
v.dispose = null; | |
} | |
}); | |
} | |
hostObject[currentSubscriptionKey] = newSubscription; | |
return newSubscription.value; | |
} | |
} | |
debugger; | |
super(getter, { dependentKeys, readOnly: true }); | |
} | |
setup(proto, propertyName) { | |
ComputedProperty.prototype.setup.apply(this, arguments); | |
debugger; | |
let dks = this._dependentKeys || []; | |
addListener(proto, "init", null, function() { | |
// get all dks to ensure observers are active (is this still necessary?) | |
this.get(propertyName); // kick off initial .get() | |
}); | |
// the observer keeps the underlying CP alive | |
addObserver(proto, propertyName, null, function() { | |
this.get(propertyName); | |
}); | |
} | |
} | |
export function subscription(...args) { | |
let subscriptionConstructor = args.pop(); | |
let dependentKeys = args; | |
return new SubscriptionManager(subscriptionConstructor, dependentKeys); | |
} | |
// const superSetup = ComputedProperty.prototype.setup; | |
// SubscriptionManagaer.prototype = Object.create(ComputedProperty.prototype); | |
// this is lifted from ember-concurrency; | |
// ember-lifeline does something like this too. | |
// TL;DR we need a better shared primitive | |
function _cleanupOnDestroy(owner, object, cleanupMethodName, ...args) { | |
// TODO: find a non-mutate-y, non-hacky way of doing this. | |
if (!owner.willDestroy) | |
{ | |
// we're running in non Ember object (possibly in a test mock) | |
return; | |
} | |
if (!owner.willDestroy.__ember_processes_destroyers__) { | |
let oldWillDestroy = owner.willDestroy; | |
let disposers = []; | |
owner.willDestroy = function() { | |
for (let i = 0, l = disposers.length; i < l; i ++) { | |
disposers[i](); | |
} | |
oldWillDestroy.apply(owner, arguments); | |
}; | |
owner.willDestroy.__ember_processes_destroyers__ = disposers; | |
} | |
owner.willDestroy.__ember_processes_destroyers__.push(() => { | |
object[cleanupMethodName](...args); | |
}); | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{ | |
"version": "0.15.1", | |
"EmberENV": { | |
"FEATURES": {} | |
}, | |
"options": { | |
"use_pods": false, | |
"enable-testing": false | |
}, | |
"dependencies": { | |
"jquery": "https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.js", | |
"ember": "3.4.3", | |
"ember-template-compiler": "3.4.3", | |
"ember-testing": "3.4.3" | |
}, | |
"addons": { | |
"ember-concurrency": "0.8.22" | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment