UNPKG

ember-lifeline

Version:

Ember.js addon for lifecycle aware async tasks and DOM events.

201 lines (164 loc) 4.78 kB
import Ember from 'ember'; import { run } from '@ember/runloop'; import { assert, deprecate } from '@ember/debug'; import getTask from './utils/get-task'; import { registerDisposable } from './utils/disposable'; const { WeakMap } = Ember; /** * A map of instances/poller functions that allows us to * store poller tokens per instance. * * @private * */ let registeredPollers = new WeakMap(); /** * Test use only. Allows for swapping out the WeakMap to a Map, giving * us the ability to detect whether the pollers set is empty. * * @private * @param {*} mapForTesting A map used to ensure correctness when testing. */ export function _setRegisteredPollers(mapForTesting) { registeredPollers = mapForTesting; } let token = 0; let _shouldPollOverride; function shouldPoll() { if (_shouldPollOverride) { return _shouldPollOverride(); } // eslint-disable-next-line ember-suave/no-direct-property-access return !Ember.testing; } export function setShouldPoll(callback) { _shouldPollOverride = callback; } let queuedPollTasks = Object.create(null); export function pollTaskFor(token) { assert( `You cannot advance pollTask '${token}' when \`next\` has not been called.`, !!queuedPollTasks[token] ); return run.join(null, queuedPollTasks[token]); } /** Sets up a function that can perform polling logic in a testing safe way. The task is invoked synchronously with an argument (generally called `next`). In normal development/production when `next` is invoked, it will trigger the task again (recursively). However, when in test mode the recursive polling functionality is disabled, and usage of the `pollTaskFor` helper is required. Example: ```js // app/components/foo-bar.js export default Component.extend({ api: injectService(), init() { this._super(...arguments); let token = this.pollTask((next) => { this.get('api').request('get', 'some/path') .then(() => { this.runTask(next, 1800); }) }); this._pollToken = token; } }); ``` Test Example: ```js import wait from 'ember-test-helpers/wait'; import { pollTaskFor } from 'ember-lifeline'; //...snip... test('foo-bar watches things', function(assert) { this.render(hbs`{{foo-bar}}`); return wait() .then(() => { assert.equal(serverRequests, 1, 'called initially'); pollTaskFor(this._pollToken); return wait(); }) .then(() => { assert.equal(serverRequests, 2, 'called again'); }); }); ``` @method pollTask @param { Function | String } taskOrName a function representing the task, or string specifying a property representing the task, which is run at the provided time specified by timeout @public */ export function pollTask(obj, taskOrName, token = getNextToken()) { let next; let task = getTask(obj, taskOrName, 'pollTask'); let tick = () => task.call(obj, next); let pollers = registeredPollers.get(obj); if (!pollers) { pollers = new Set(); registeredPollers.set(obj, pollers); registerDisposable(obj, getPollersDisposable(obj, pollers)); } pollers.add(token); if (shouldPoll()) { next = tick; } else { next = () => { queuedPollTasks[token] = tick; }; } task.call(obj, next); return token; } /** Clears a previously setup polling task. Example: ```js // app/components/foo-bar.js export default Component.extend({ api: injectService(), enableAutoRefresh() { this._pollToken = pollTask(this, (next) => { this.get('api').request('get', 'some/path') .then(() => { runTask(this, next, 1800); }) }); }, disableAutoRefresh() { cancelPoll(this, this._pollToken); } }); ``` @method cancelPoll @param { String } token the token for the pollTask to be cleared @public */ export function cancelPoll(obj, token) { if (token === undefined) { deprecate( 'ember-lifeline cancelPoll called without an object. New syntax is cancelPoll(obj, cancelId) and avoids a memory leak.', true, { id: 'ember-lifeline-cancel-poll-without-object', until: '4.0.0', } ); token = obj; } else { let pollers = registeredPollers.get(obj); pollers.delete(token); } delete queuedPollTasks[token]; } function getPollersDisposable(obj, pollers) { return function() { pollers.forEach(token => { cancelPoll(obj, token); }); }; } function getNextToken() { return token++; }