ember-concurrency icon indicating copy to clipboard operation
ember-concurrency copied to clipboard

Consider putting the cancellation policy/lifetime, etc, on the callsite

Open chancancode opened this issue 8 years ago • 0 comments

Example app: https://ember-twiddle.com/4adde6ca35c70ee3ae57e8a10cf55176?openFiles=controllers.search.js%2C

  1. Since tasks can be parameterized, it seems incorrect to tie the lifetime/cancellation policy to the task description. In the example, there are two consumers/callsites to the search task, you want each of them to be restartable but allow both of them to run concurrently. (You can make a sub-task to work around it.)

  2. Tasks are not necessarily performed on the same object they are defined. For example, the search task could have been defined on a service, and invoked in a component via this.get('github.search').perform(...), in which case, e-c will not be able to tie this task to the component's lifetime correctly.

  3. We want a way to mark a task as dependent on its inputs and invalidate the result when they changes. @machty and I came up with the CP-pattern in the example which largely works, but again, you want each of the performs to be restartable, not the whole task.

  4. Sometimes you want to perform a "fire-and-forget" task without linking its lifetime to the component.

Strawman:

import Ember from 'ember';
import { task } from 'ember-concurrency';
const { computed } = Ember;

function *reportUsage(ajax, searches) {
  return ajax.post(`/analytics`, { searches });
}

export default Ember.Controller.extend({
  ...,

  searches: 0,

  search: function * (type, query) {
    let result = yield this.get('ajax').request(`https://api.github.com/search/${type}?q=${query}`);
    this.incrementProperty('searches');
    return result.items;
  },

  userSearch: computed('query', function() {
    let query = this.get('query');
    return this.task('search') // <- sugar for `task(this.get('search').bind(this)).link(this) ?
      .restartable('userSearch') // <- specify a unique id/scope, defaults to the identity of the function?
      .perform('users', query);
  }),

  repoSearch: computed('query', function () {
    let query = this.get('query');
    return this.task('search')
      .restartable('repoSearch')
      .perform('repositories', query);
  }),

  willDestroy() {
    let { ajax, searches } = this.getProperties('ajax', 'searches');

    // Fire-and-forget: unlike `this.task`, the bare `task` function does not link by default,
    // so this doesn't cancel itself when the component/controller dies
    task(reportUsage).perform(ajax, searches);

    this._super();
  }
});

chancancode avatar Jun 15 '17 00:06 chancancode