GithubHelp home page GithubHelp logo

ember-lifeline / ember-lifeline Goto Github PK

View Code? Open in Web Editor NEW
238.0 9.0 54.0 15.71 MB

An Ember addon for managing the lifecyle of asynchronous behavior in your objects

Home Page: https://github.com/ember-lifeline/ember-lifeline/wiki

License: MIT License

JavaScript 55.05% HTML 3.45% TypeScript 41.18% CSS 0.24% Handlebars 0.08%
ember ember-lifeline ember-addon runloop

ember-lifeline's Introduction

ember-lifeline

CI Build Ember Observer Score npm version Monthly Downloads from NPM Code Style: prettier

Ember applications have long life-cycles. A user may navigate to several pages and use many different features before they leave the application. This makes JavaScript and Ember development unlike Rails development, where the lifecycle of a request is short and the environment disposed of after each request. It makes Ember development much more like iOS or video game development than traditional server-side web development.

This addon introduces several functional utility methods to help manage async, object lifecycles, and the Ember runloop. These tools should provide a simple developer experience that allows engineers to focus on the business domain, and think less about the weird parts of working in a long-lived app.

The documentation wiki contains more examples and API information.

Compatibility

  • Ember.js v3.28 or above
  • Ember CLI v3.28 or above
  • Node.js v16 or above

Installation

ember install ember-lifeline

Usage

Ember Lifeline supports a functional API that enables entanglement - the association of async behavior to object instances. This allows you to write async code in your classes that can be automatically cleaned up for you when the object is destroyed.

Ember's runloop functions, like the example below, don't ensure that an object's async is cleaned up.

import Component from '@glimmer/component';
import { tracked } from '@glimmer/tracking';
import { run } from '@ember/runloop';

export default class Example extends Component {
  @tracked date;
  
  constructor() {
    super(...arguments);

    run.later(() => {
      this.date = new Date();
    }, 500);
  }
}

Using ember-lifeline's equivalent, in this case runTask, can help ensure that any active async is cleaned up once the object is destroyed.

import Component from '@glimmer/component';
import { tracked } from '@glimmer/tracking';
import { runTask } from 'ember-lifeline';

export default class Example extends Component {
  @tracked date;
  
  constructor() {
    super(...arguments);

    runTask(
      this,
      () => {
        this.date = new Date();
      },
      500
    );
  }
}

For more information and examples, please visit the documentation wiki.

Contributing

See the Contributing guide for details.

Credit

This addon was developed internally at Twitch, written originally by @mixonic and @rwjblue. It's since been further developed and maintained by scalvert.

The name ember-lifeline was suggested by @nathanhammod.

ember-lifeline's People

Contributors

bekzod avatar bryancrotaz avatar buschtoens avatar chriskrycho avatar dependabot-preview[bot] avatar dependabot-support avatar dependabot[bot] avatar elwayman02 avatar ember-tomster avatar evanfarina avatar heroiceric avatar jackson-dean avatar jrjohnson avatar jsturgis avatar kratiahuja avatar mdebbar avatar mehulkar avatar mike-north avatar nathanhammond avatar nlfurniss avatar offirgolan avatar rwjblue avatar scalvert avatar schneiderl avatar sduquej avatar simonihmig avatar step2yeung avatar tgvrssanthosh avatar twokul avatar yoranbrondsema avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

ember-lifeline's Issues

Add support for Ember.Evented?

Issue Type

Feature Request/RFC

Desc

Would it be possible to add a new api or overload addEventListener to support subscribing to Ember.Evented?

We utilize some pub/sub services that require similar cleanup, state checking, and testing acrobats that are solved by this library in the jQuery case.

Sorry if this is a silly suggestion or out of scope for this repo.

throttleTask doesn't pass through additional args

Readme says:

throttleTask(obj, methodName, args*, spacing, immediate)

But the source code doesn't pass through args* and immediate:

export function throttleTask(obj, name, timeout = 0) {
assert(
`Called \`throttleTask\` without a string as the first argument on ${obj}.`,
typeof name === 'string'
);
assert(
`Called \`throttleTask('${name}', ${timeout})\` where '${name}' is not a function.`,
typeof obj[name] === 'function'
);
assert(
`Called \`throttleTask\` on destroyed object: ${obj}.`,
!obj.isDestroyed
);
let timers = getTimers(obj);
let cancelId = run.throttle(obj, name, timeout);
timers.add(cancelId);
return cancelId;
}

Why block calls to tasks on a destroyed object when you could return early?

The README.md states that this addon "should provide a simple developer experience that allows engineers to focus on the business domain, and think less about the weird parts of working in a long-lived app." However, when integrating the addon into secondstreet/ember-material-components-web/pull/71 I found I had to check for object.isDestroyed before calling the ember-lifeline's scheduling tasks.

I would prefer for ember-lifeline to not throw when the object passed in isDestroyed. What it should do can be up for discussion.

Loosen type from EmberObject to interface

Currently, most of the functions provided require an EmberObject to be provided as a way to key the WeakMaps. We should loosen this type to be an interface that conforms to a specific shape of what we expect. This will help the move to using native classes.

Fails to compile in typescript

Addon uses ember-cli-typescript and ember-lifeline. When building its dummy app or during the precompile step of npm publish I get the following errors:

node_modules/ember-lifeline/dom-event-listeners.d.ts(45,114): error TS2304: Cannot find name 'RunMethod'.

node_modules/ember-lifeline/dom-event-listeners.d.ts(53,117): error TS2304: Cannot find name 'RunMethod'.

node_modules/ember-lifeline/run-task.d.ts(11,78): error TS2304: Cannot find name 'EmberRunTimer'.

node_modules/ember-lifeline/run-task.d.ts(45,94): error TS2304: Cannot find name 'EmberRunTimer'.

node_modules/ember-lifeline/run-task.d.ts(80,67): error TS2304: Cannot find name 'EmberRunQueues'.

node_modules/ember-lifeline/run-task.d.ts(80,124): error TS2304: Cannot find name 'EmberRunTimer'.

node_modules/ember-lifeline/run-task.d.ts(112,90): error TS2304: Cannot find name 'EmberRunTimer'.

node_modules/ember-lifeline/run-task.d.ts(144,46): error TS2304: Cannot find name 'EmberRunTimer'.

node_modules/ember-lifeline/run-task.d.ts(145,64): error TS2304: Cannot find name 'EmberRunTimer'.

memory leak in scheduleTask

weak map is not being cleaned up.

getTimersDisposable leaves an entry in the registeredTimers WeakMap and individual cancelIds are left after tasks have completed

IE11 support

We just noticed (in production ๐Ÿคฆโ€โ™‚๏ธ ), that ember-lifeline does not support IE11. Running the tests in IE11 shows, that 18 out of 114 tests are failing. I've started a branch to fix this and will submit a PR soon.

Automatically reopen primary classes to add mixin.

Specifically, mix ember-lifeline/mixins/run into the following classes:

  • Ember.Component
  • Ember.Route
  • Ember.Controller
  • Ember.Helper
  • Ember.Service

And mix ember-lifeline/mixins/dom into:

  • Ember.Component

Issue with @types/ember after upgrade

After upgrading to the latest version of @types/ember, we get a number of compilation issues during ember ts:precompile.

Command failed: tsc --outDir /private/var/folders/3q/5x6gzths7b97lq08vx_cb0g4000gyd/T/e-c-ts-precompile-87505 --rootDir /Users/scalvert/Workspace/ember-lifeline --allowJs false --noEmit false --declaration --sourceMap false --inlineSourceMap false --inlineSources false

node_modules/@types/ember/index.d.ts(199,11): error TS2506: 'ComputedProperty' is referenced directly or indirectly in its own base expression.
node_modules/@types/ember/index.d.ts(215,11): error TS2506: 'Object' is referenced directly or indirectly in its own base expression.
node_modules/@types/ember/index.d.ts(216,11): error TS2506: 'ObjectProxy' is referenced directly or indirectly in its own base expression.
node_modules/@types/ember/index.d.ts(217,11): error TS2502: 'Observable' is referenced directly or indirectly in its own type annotation.
node_modules/@types/ember/index.d.ts(218,11): error TS2502: 'PromiseProxyMixin' is referenced directly or indirectly in its own type annotation.
node_modules/@types/ember/index.d.ts(219,11): error TS2506: 'CoreObject' is referenced directly or indirectly in its own base expression.
node_modules/@types/ember/index.d.ts(231,25): error TS2693: 'EmberError' only refers to a type, but is being used as a value here.
node_modules/@types/ember/index.d.ts(233,11): error TS2502: 'Evented' is referenced directly or indirectly in its own type annotation.
node_modules/@types/ember/index.d.ts(287,11): error TS2506: 'Mixin' is referenced directly or indirectly in its own base expression.
node_modules/@types/ember/index.d.ts(446,11): error TS2502: 'computed' is referenced directly or indirectly in its own type annotation.
node_modules/@types/ember/index.d.ts(460,11): error TS2502: 'defineProperty' is referenced directly or indirectly in its own type annotation.
node_modules/@types/ember/index.d.ts(464,11): error TS2502: 'cacheFor' is referenced directly or indirectly in its own type annotation.
node_modules/@types/ember/index.d.ts(465,11): error TS2502: 'addListener' is referenced directly or indirectly in its own type annotation.
node_modules/@types/ember/index.d.ts(466,11): error TS2502: 'removeListener' is referenced directly or indirectly in its own type annotation.
node_modules/@types/ember/index.d.ts(467,11): error TS2502: 'sendEvent' is referenced directly or indirectly in its own type annotation.
node_modules/@types/ember/index.d.ts(468,11): error TS2502: 'on' is referenced directly or indirectly in its own type annotation.
node_modules/@types/ember/index.d.ts(476,11): error TS2502: 'aliasMethod' is referenced directly or indirectly in its own type annotation.
node_modules/@types/ember/index.d.ts(477,11): error TS2502: 'observer' is referenced directly or indirectly in its own type annotation.
node_modules/@types/ember/index.d.ts(478,11): error TS2502: 'addObserver' is referenced directly or indirectly in its own type annotation.
node_modules/@types/ember/index.d.ts(479,11): error TS2502: 'removeObserver' is referenced directly or indirectly in its own type annotation.
node_modules/@types/ember/index.d.ts(480,11): error TS2502: 'get' is referenced directly or indirectly in its own type annotation.
node_modules/@types/ember/index.d.ts(481,11): error TS2502: 'getWithDefault' is referenced directly or indirectly in its own type annotation.
node_modules/@types/ember/index.d.ts(482,11): error TS2502: 'getProperties' is referenced directly or indirectly in its own type annotation.
node_modules/@types/ember/index.d.ts(483,11): error TS2502: 'setProperties' is referenced directly or indirectly in its own type annotation.
node_modules/@types/ember/index.d.ts(484,11): error TS2502: 'set' is referenced directly or indirectly in its own type annotation.
node_modules/@types/ember/index.d.ts(485,11): error TS2502: 'trySet' is referenced directly or indirectly in its own type annotation.
node_modules/@types/ember/index.d.ts(491,11): error TS2502: 'copy' is referenced directly or indirectly in its own type annotation.
node_modules/@types/ember/index.d.ts(505,11): error TS2502: 'guidFor' is referenced directly or indirectly in its own type annotation.
node_modules/@types/ember/index.d.ts(530,11): error TS2502: 'expandProperties' is referenced directly or indirectly in its own type annotation.
node_modules/@types/ember/node_modules/@types/ember__component/-private/core-view.d.ts(11,51): error TS2339: Property 'extend' does not exist on type 'typeof EmberObject'.
node_modules/@types/ember/node_modules/@types/ember__component/index.d.ts(24,49): error TS2339: Property 'extend' does not exist on type 'typeof CoreView'.
node_modules/@types/ember/node_modules/@types/ember__component/text-area.d.ts(8,49): error TS2339: Property 'extend' does not exist on type 'typeof Component'.
node_modules/@types/ember/node_modules/@types/ember__component/text-field.d.ts(7,50): error TS2339: Property 'extend' does not exist on type 'typeof Component'.
node_modules/@types/ember/node_modules/@types/ember__object/computed.d.ts(4,15): error TS2502: 'ComputedProperty' is referenced directly or indirectly in its own type annotation.
node_modules/@types/ember/node_modules/@types/ember__object/computed.d.ts(13,14): error TS2502: 'expandProperties' is referenced directly or indirectly in its own type annotation.
node_modules/@types/ember/node_modules/@types/ember__object/core.d.ts(3,22): error TS2506: 'CoreObject' is referenced directly or indirectly in its own base expression.
node_modules/@types/ember/node_modules/@types/ember__object/evented.d.ts(3,22): error TS2694: Namespace '"/Users/scalvert/Workspace/ember-lifeline/node_modules/@types/ember/index".Ember' has no exported member 'Evented'.
node_modules/@types/ember/node_modules/@types/ember__object/evented.d.ts(4,15): error TS2502: 'Evented' is referenced directly or indirectly in its own type annotation.
node_modules/@types/ember/node_modules/@types/ember__object/evented.d.ts(6,14): error TS2502: 'on' is referenced directly or indirectly in its own type annotation.
node_modules/@types/ember/node_modules/@types/ember__object/events.d.ts(3,14): error TS2502: 'addListener' is referenced directly or indirectly in its own type annotation.
node_modules/@types/ember/node_modules/@types/ember__object/events.d.ts(4,14): error TS2502: 'removeListener' is referenced directly or indirectly in its own type annotation.
node_modules/@types/ember/node_modules/@types/ember__object/events.d.ts(5,14): error TS2502: 'sendEvent' is referenced directly or indirectly in its own type annotation.
node_modules/@types/ember/node_modules/@types/ember__object/index.d.ts(8,15): error TS2506: 'EmberObject' is referenced directly or indirectly in its own base expression.
node_modules/@types/ember/node_modules/@types/ember__object/index.d.ts(10,14): error TS2502: 'aliasMethod' is referenced directly or indirectly in its own type annotation.
node_modules/@types/ember/node_modules/@types/ember__object/index.d.ts(11,14): error TS2502: 'computed' is referenced directly or indirectly in its own type annotation.
node_modules/@types/ember/node_modules/@types/ember__object/index.d.ts(12,14): error TS2502: 'defineProperty' is referenced directly or indirectly in its own type annotation.
node_modules/@types/ember/node_modules/@types/ember__object/index.d.ts(13,14): error TS2502: 'get' is referenced directly or indirectly in its own type annotation.
node_modules/@types/ember/node_modules/@types/ember__object/index.d.ts(14,14): error TS2502: 'getProperties' is referenced directly or indirectly in its own type annotation.
node_modules/@types/ember/node_modules/@types/ember__object/index.d.ts(15,14): error TS2502: 'getWithDefault' is referenced directly or indirectly in its own type annotation.
node_modules/@types/ember/node_modules/@types/ember__object/index.d.ts(16,14): error TS2502: 'observer' is referenced directly or indirectly in its own type annotation.
node_modules/@types/ember/node_modules/@types/ember__object/index.d.ts(17,14): error TS2502: 'set' is referenced directly or indirectly in its own type annotation.
node_modules/@types/ember/node_modules/@types/ember__object/index.d.ts(18,14): error TS2502: 'setProperties' is referenced directly or indirectly in its own type annotation.
node_modules/@types/ember/node_modules/@types/ember__object/index.d.ts(19,14): error TS2502: 'trySet' is referenced directly or indirectly in its own type annotation.
node_modules/@types/ember/node_modules/@types/ember__object/internals.d.ts(3,14): error TS2502: 'cacheFor' is referenced directly or indirectly in its own type annotation.
node_modules/@types/ember/node_modules/@types/ember__object/internals.d.ts(4,14): error TS2502: 'copy' is referenced directly or indirectly in its own type annotation.
node_modules/@types/ember/node_modules/@types/ember__object/internals.d.ts(5,14): error TS2502: 'guidFor' is referenced directly or indirectly in its own type annotation.
node_modules/@types/ember/node_modules/@types/ember__object/mixin.d.ts(3,22): error TS2506: 'Mixin' is referenced directly or indirectly in its own base expression.
node_modules/@types/ember/node_modules/@types/ember__object/observable.d.ts(3,25): error TS2694: Namespace '"/Users/scalvert/Workspace/ember-lifeline/node_modules/@types/ember/index".Ember' has no exported member 'Observable'.
node_modules/@types/ember/node_modules/@types/ember__object/observable.d.ts(4,15): error TS2502: 'Observable' is referenced directly or indirectly in its own type annotation.
node_modules/@types/ember/node_modules/@types/ember__object/observers.d.ts(3,14): error TS2502: 'addObserver' is referenced directly or indirectly in its own type annotation.
node_modules/@types/ember/node_modules/@types/ember__object/observers.d.ts(4,14): error TS2502: 'removeObserver' is referenced directly or indirectly in its own type annotation.
node_modules/@types/ember/node_modules/@types/ember__object/promise-proxy-mixin.d.ts(3,35): error TS2694: Namespace '"/Users/scalvert/Workspace/ember-lifeline/node_modules/@types/ember/index".Ember' has no exported member 'PromiseProxyMixin'.
node_modules/@types/ember/node_modules/@types/ember__object/promise-proxy-mixin.d.ts(4,15): error TS2502: 'PromiseProxyMixin' is referenced directly or indirectly in its own type annotation.
node_modules/@types/ember/node_modules/@types/ember__object/proxy.d.ts(3,22): error TS2506: 'ObjectProxy' is referenced directly or indirectly in its own base expression.
node_modules/@types/jquery/index.d.ts(6366,66): error TS2344: Type '"timeout" | "onreadystatechange" | "responseType" | "withCredentials" | "msCaching"' does not satisfy the constraint '"abort" | "open" | "timeout" | "getAllResponseHeaders" | "getResponseHeader" | "overrideMimeType" | "readyState" | "responseText" | "setRequestHeader" | "status" | "statusText" | ... 22 more ... | "dispatchEvent"'.
  Type '"msCaching"' is not assignable to type '"abort" | "open" | "timeout" | "getAllResponseHeaders" | "getResponseHeader" | "overrideMimeType" | "readyState" | "responseText" | "setRequestHeader" | "status" | "statusText" | ... 22 more ... | "dispatchEvent"'.

Ability to convert Ember.Evented usage

Ability to replace Ember.Evented with an comparable API would be great. Example:

Current

// app/services/foo.js
import Evented from '@ember/object/evented';
export default Service.extend(Evented, {
  triggerEvent() {
    this.trigger('someEvent');
  }
});

// app/components/foo-bar.js
export default Component.extend({
  foo: service(),

  init() {
    this._super(...arguments);
    this.get('foo').on('someEvent', () => alert('called'))
  }
})

Desired

I think a good way to do this would be to add trigger to the ContextBoundEventListenersMixin, so that it can be mixed into any object and can trigger events and register listeners.

// app/services/foo.js
import { ContextBoundEventListenersMixin } from 'ember-lifeline';

export default Service.extend(ContextBoundEventListenersMixin, {
  triggerEvent() {
    this.trigger('someEvent');
  }
});

// app/components/foo-bar.js
import { addEventListener, runDisposables } from 'ember-lifeline';
export default EmberObject.extend({
  foo: service(),

  init() {
    this._super(...arguments);
    addEventListener(this, this.get('foo'), 'someEvent', () => alert('called'));
  }

  willDestroy() {
    runDisposables(this);
  }
})

Thoughts?

assert.expect has unexpected behavior

Since ember-lifeline adds an assertion into QUnit.testDone, any user calls to assert.expect need to account for an extra assertion, which is weird.

Maybe the assertion from the addon can be changed to a warning instead?

Expose a `this.cancelTask` method

Currently, the only way to manually cancel a task is to use Ember.run.cancel(cancelId) but that's leaking an implementation details.

const cancelId = this.runTask(reject, this.timeout);
// ... later
Ember.run.cancel(cancelId);

Ideally, the cancelId should be opaque to the caller. The caller shouldn't know that it's coming from Ember.run. We should add either:

const cancelId = this.runTask(reject, this.timeout);
// ... later
this.cancelTask(cancelId);

or:

const cancelTask = this.runTask(reject, this.timeout);
// ... later
cancelTask();

Assertion in `addEventListener` does not line up with what is advertised in the docs

assertArguments(target, eventName, callback);

This assertion seems to assert that the second argument must be a DOM element and not a string, etc. However, this is not in line with the current docs:

addEventListener will provide the runloop and remove the listener when destroy is called, provided runDisposables is called. addEventListener provides several ways to specify an element:

addEventListener(this, '.someClass', 'scroll', fn);

// Attach to the component's element itself
addEventListener(this, 'scroll', fn);

// Attach to a DOM node
addEventListener(this, document.body, 'click', fn);

// Attach to window
addEventListener(this, window, 'scroll', fn);```

pollTask always runs the first poll?

I have a component that updates every 30 seconds. Since adding in pollTask my acceptance tests now work and no longer hang because of the nested runloop laters... however they always pause for the first 30 seconds, obviously dramatically slowing down my tests.

Is there any way I can tell Lifeline to simply never run the poll after the component init?

DEPRECATION: ember-cli-babel 5.x has been deprecated

After installing ember-lifeline in an Ember project, following error appears when running the server:

DEPRECATION:ember-cli-babel 5.x has been deprecated. Please upgrade to at least ember-cli-babel 6.6.0

Environment

Ember Version: 2.18.2
Ember CLI Version: 2.18.2

Steps to Reproduce

In the Ember project install ember-lifeline:

ember install ember-lifeline

After that run an Ember command like:

ember -v

or:

ember s

`this.throttleTask` doesn't allow extra arguments

What's the best way to convert Ember.run.throttle('myMethod', arg1, arg2, 100) to ember-lifeline's alternative?

this.throttleTask expects the first argument to be the method name, so we can't pass a curried function or an arrow function. And it doesn't accept extra arguments like its Ember.run counterpart.

cc @scalvert @rwjblue

After Ember 3.2 upgrade, didRender() hook in a component with poll is not fired appropriately in qunit tests

I have been using ember lifeline's pollTask to animate a sliding progress bar component because it provides the easiest test interface for testing the different steps. The component has 5 segments that animate over different lengths of time by applying different CSS styles. In my tests, I test that after each pollTaskFor the appropriate CSS class has been applied.

When I upgrade my app from Ember 3.1 to Ember 3.2, this test starts to fail. It sees that the 1st and 3rd classes are applied, but not for 2nd, 4th, or 5th (I implemented a workaround to wait for settledness for the last one). After digging in a bit more, I see that the 2nd and 4th classes are NOT applied before the assert is run in the test, and it seems to be because the didRender() hook on the component is not called after these polls, but it is called after the 3rd. I don't understand why it is called sometimes and not other times, and why this would have changed when going from Ember 3.1 to Ember 3.2.

I pulled out the progress bar component into its own Ember app, and the test failure is still occurring in QUnit. I also tried upgrading the sample app in incremental minor version steps all the way up to 3.8, and the test failures never resolved. When I reverted back to Ember 3.1, the tests passed again.

You can see this app and download it yourself here: https://github.com/ajcolter/ember-progress-bar
I left a few branches open for the different versions of Ember. The master branch is on 3.2

destruct Mixin, assert, run from Ember

Suggestion

In the preamble to https://github.com/rwjblue/ember-lifeline/blob/master/addon/mixins/run.js, we see:

import Ember from 'ember';
import Mixin from 'ember-metal/mixin';
import run from 'ember-runloop';
import { assert } from 'ember-metal/utils';

Please consider changing to:

import Ember from 'ember';
const { Mixin, run, assert } = Ember;

Why

While this isn't a big deal to most ember users in the world of 2.x.x world, a lot of enterprise applications of Ember are still stuck in the 1.x.x world where ember-metal and ember-runloop aren't a thing yet. I know it's our own fault for having forefathers whom have sinned and written cancer code that now keeps us stuck in EoL version of Ember...

But haven't we suffered enough? So our forefathers sinned; so we ignored the best practices of DDAU, spammed controller everywhere, and have window.setInterval peppered all over our app. But in order for us to move forward, we need addons like this to fix our problems. And the fact that we who need it the most can't access it is quite a painful irony.

Please, make the change.

Also, I can make PR and write up a test if you guys are too busy

Workaround

But what if @rwjblue has no mercy for us sinners? Here's what we must do to work-around (aka atone):

https://gist.github.com/foxnewsnetwork/71ed4b5c973e0c2249b80dd421e99acd

Watch / polyfill / update ongoing "destructors" RFC

Just creating this issue to remind us that we need to keep track of emberjs/rfcs#580 as it progresses through the RFC process, and likely:

  • create a dedicated polyfill addon (under ember-polyfills org)
  • add that as a dependency, and update our internal destroyables implementation to leverage it

"registeredDisposables.keys is not a function" when two tests in one module

When I run these two tests I get an error on the second test:

afterEach failed on visiting /posts : registeredDisposables.keys is not a function
import { module, test } from 'qunit';
import { setupApplicationTest } from 'ember-qunit';
import { visit, currentURL } from '@ember/test-helpers';
import setupLifelineValidation from 'ember-lifeline/test-support';

module('Acceptance | clean up', hooks => {
  setupLifelineValidation(hooks);
  setupApplicationTest(hooks);

  test('visiting /posts', async assert => {
    await visit('/posts');
    assert.equal(currentURL(), '/posts');
  });

  test('visiting /posts', async assert => {
    await visit('/posts');
    assert.equal(currentURL(), '/posts');
  });
});

It looks like this is because in the afterEach hook, registeredDisposables is assigned to a WeakMap in the same closure as the beforeEach, causing the cleanup step to blow up.

The solution is probably a one-line assignment to registeredDisposables = new Map(); in hooks.beforeEach if I understand the problem and intention of this hook correctly .

[IE11] support for `{ once: true }`

IE11 does not natively support { once: true }. While I am not using it, I think this is a crucial feature to support or, if not, at least explicitly document that it is not supported. Otherwise users will have a bad time when their app behaves differently in IE11.

Relates to #267, #266.

Maybe pollTaskFor should wait()?

My tests fail if I don't wait() before and after pollTaskFor:

await wait();
await pollTaskFor('label');
await wait();

The tests in this repo also seem to doing that. Any reason it can't be rolled into pollTaskFor?

PollTask doesn't check if the task is cancelled or not

When the runTask is scheduled in an async behavior while we cancel the pollTask and runTask using cancelPoll and cancelTask respectively.

this.get('store')
      .findRecord('model')
      .finally(() => {
          this._runTask = runTask(this, next, 5);
        }
      });

the next function doesn't check if the corresponding pollTask token in the poller queue is removed or not and never cancels the task.

let tick = () => task.call(destroyable, next);

Although there is a workaround where the caller can track whether it should call or not next based on its own logic when it should stop polling.

this.get('store')
      .findRecord('model')
      .finally(() => {
          if(this._runPoll) {
              this._runTask = runTask(this, next, 5);
          }
        }
      });

scheduleTask fails if target object is already destroyed

Thoughts - scheduleTask is intended to save the dev from the dreaded isDestroying checks.

Consider:

<img onerror={{action imgError}} />
...
imgError() {
  scheduleTask(this, 'actions', () => {
    this.broken = true;
  });
}

If the component is torn down before the dom event fires, this can cause

Assertion Failed: Called `scheduleTask` on destroyed object: <dummy@component:my-image::ember770>

As scheduleTask is supposed to protect the dev from this on the event firing, maybe it should also check when scheduleTask is called and just exit silently?

task labels seem problematic

https://github.com/rwjblue/ember-lifeline/blob/master/addon/mixins/run.js#L22-L24

These labels are global, this means that (for example) two components using the same labels will interfere with each other.

If these labels are used, they should most likely be scoped to the this in question, and not made global, or sad interleavings will occure.

I also believe these label methods should return explicit tokens to still explicitly cancel. Which would address the above described issues.

Pass arguments into `runTask`

I think it should be possible to pass arguments to runTask

// this.runTask(callbackOrName, timeout, ...args)
this.runTask('myTask', 500, 'foo', 'bar');

pollTask in service degrades acceptance test performance

I noticed an increase in total test time when I used pollTask in a service.

interval =  100 ms -> 33 seconds
interval =  500 ms -> 53 seconds
interval = 1000 ms -> 82 seconds

This is the relevant service code:

// app/services/clock.js
init() {
  this._super(...arguments);
  this.pollTask('update', 'clock#update');
},
update(next) {
  let serverTime = this.get('serverTime');
  if (serverTime) {
    // ...
    this.set('serverTime', new Date(localMillis - totalOffset));
  }
  this.runTask(next, 500); // <-- 500 ms interval
},

and the one test that actually uses pollTaskFor is something like:

// tests/acceptance/clock-test.js
const mockTimeService = Service.extend({
  now() {
    return clientTime;
  },
  offset() {
    return clientOffset;
  }
});

//...

await login.visit();

await assert.equal(clock.date, '16 Mar 2017', 'clock shows date');
await assert.equal(clock.time, '6:30:20 pm', 'clock shows time');
await assert.equal(clock.timezone, 'Pacific', 'clock shows timezone');

clientTime = moment('2018-04-17T19:35:25.100', 'YYYY-MM-DDTHH:mm:ss.SSS').toDate();
await pollTaskFor('clock#update');

await assert.equal(clock.date, '17 Apr 2018', 'updated clock shows date');
await assert.equal(clock.time, '3:35:25 pm', 'updated clock shows time');
await assert.equal(clock.timezone, 'Pacific', 'updated clock shows timezone');

The test really doesn't effect anything, I just think the async/await combined with the pollTaskFor is just super nice looking ๐Ÿ˜ thanks for the awesome work!

One thing that may be causing this is that I'm mixing this into a service, not a component. I tried debugging this a bit and it seems like runTask is the culprit. Without it the test suite drops back down to 29 seconds.

I profiled when the polling started and when the timers where cleared and this is what I got (with an interval of 1000 ms):

pollTask 851 ms
runTask 853 ms
willDestroy 1901 ms

It may be that the services willDestroy hook is not called until all tasks in the run loop have finished but I'm not very familiar with the inner workings of the run loop.

As a temporary fix I'm setting the interval to 0 in test:

let interval = 500;
if (Ember.testing) {
  interval = 0;
}
this.runTask(next, interval);

Enable usage of `Ember.run.schedule`

This could either be via options passed to runTask, or another top level method specifically for this (i.e. scheduleTask).

As of emberjs/ember.js#14550 (will be included in 2.8.3, 2.9.1, 2.10.0-beta.3+), it is possible to cancel things scheduled into a specific queue via Ember.run.schedule. In a sample application run.schedule is one of the primary remaining use cases of Ember.run that still requires manual this.isDestroying checks.

Typescript errors when running in host app

Upgraded my application to [email protected] from @1.5.0 and [email protected] from @6.18.0 and got the following errors:

ember s
node_modules/ember-lifeline/mixins/dom.d.ts:55:99 - error TS2304: Cannot find name 'RunMethod'.

55     addEventListener(this: MaybeIsComponent, target: TargetOrString, eventName: string, callback: RunMethod<MaybeIsComponent, any>, options?: Object | undefined): void;
                                                                                                     ~~~~~~~~~

node_modules/ember-lifeline/mixins/dom.d.ts:62:102 - error TS2304: Cannot find name 'RunMethod'.

62     removeEventListener(this: MaybeIsComponent, target: TargetOrString, eventName: string, callback: RunMethod<MaybeIsComponent, any>, options?: Object | undefined): void;
                                                                                                        ~~~~~~~~~

`addEventListener` does not work with jQuery objects

This change removed the support for passing jQuery objects to addEventListener (see here for the current state of the file addon/mixins/dom.js). I'm not sure if that was intentional as the documentation still says that they should work.

So either the documentation should be updated or the support for jQuery objects should be added back to the addon. I don't mind fixing either of these but I just wanted to make sure what the intention was before I started the work.

Idea: onTask offTask OneTask

Just a quick sketch before I forget:

it is not uncommon to leak event emitter handles, for example:

export default Component.extend({
  init() {
    this._super(...arguments);
    this.lookup('service:router').on('didTransition', this.something); 
  },

  something() { }
})

We must remember to add the symmetric off

destroy() {
    this.lookup('service:router').off('didTransition', this.something); 
}

But then we refactor to bind this.something and we once again leak.

export default Component.extend({
  init() {
    this._super(...arguments);
    this.lookup('service:router').on('didTransition', this.something.bind(this)); 
  },
  destroy() {
    this.lookup('service:router').off('didTransition', this.something); 
  },
 something() { }

})

To correct this, we must also save of the bound method:

export default Component.extend({
  init() {
    this._super(...arguments);
     this._boundSomething = this.something.bind(this)
    this.lookup('service:router').on('didTransition', this._boundSomething ); 
  },
  destroy() {
    this.lookup('service:router').off('didTransition', this._boundSomething); 
  },
 something() { }

})

Which is both verbose and a constant source of bugs.

Instead, it would be nice if we had some lifeline aware emitter (I have no idea what to call it, but lets call it onTask, oneTask and offTask for this example).

this.onTask(emitter, eventName, callback); // emitter.on(eventName, callback) but lifeline entangled
this.oneTask(emitter, eventName, otherCallback); // emitter.one(eventName, callback) but lifeline entangled
this.offTask(emitter, eventName, callback);  // emitter.off(eventName, callback) but lifeline entangled

Full example becomes:

And as a bonus, arrow functions work!!

export default Component.extend({
  init() {
    this._super(...arguments);
    this.onTask(this.lookup('service:router'), 'didTransition', () => this.something());
  },
 something() { }

})

Handling async functions/actions

Are there any ideas for handling lifecycles with async code beyond events and timeout stuff?

async toggleDocuments() {
  let documents = this.get('documents');
  let show = this.toggleProperty('showDocuments');

  if (show && !documents) {
    let documents = await this.loadDocuments();

    // to not need this part
    if (this.isDestroyed || this.isDestroying) {
      return;
    }

    this.set('documents', documents);
  }
}

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.