question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Getting rid of `.perform()` in JS

See original GitHub issue

tl;dr

// I want
this.someTask('🥦');
this.someTask.performCount; // => 1

// instead of
this.someTask.perform('🥦');
this.someTask.performCount; // => 1

We already took the first step

This nifty trick allows users to just use the {{action}} helper with tasks in their templates:

https://github.com/machty/ember-concurrency/blob/5c830b1dfdc9609b4de841b503bdb36023bbb4b1/addon/-task-property.js#L395-L397

You’d use it as:

<button onclick={{action deleteUser user}}>
  Delete {{user.name}}
</button>

This is super sexy. 🔥

The templates are agnostic to whether or not the thing that you’re passing is a regular (closure) action or a task.

I would love to have the equivalent in JS and get rid of .perform().

It’s already improved in JS

Since ES5 getters landed, ergonomics already improved massively for tasks, because instead of this:

export default Ember.Controller.extend({
  someTask: task(function*() {
    console.log('😐');
  }),
  
  actions: {
    performSomeTask() {
      this.get('someTask').perform();
    }
  }
});

You can know just go (Ember Twiddle):

export default Ember.Controller.extend({
  someTask: task(function*() {
    console.log('😍');
  }),
  
  actions: {
    performSomeTask() {
      this.someTask.perform();
    }
  }
});

One last step

But what’s still annoying is that, when you refactor a simple method to a task, you have to update all the places that call this method, because you need to switch from:

this.someMethod('🥦');

to:

this.someMethod.perform('🥦');

What would it take?

The TaskProperty, when it is .get()ed should not return an object that is a Task, but instead should return a function that has all the Task stuff on its prototype (?). There might be some mad science involved, but assuming that

a) this does not hurt performance b) we do not remove .perform() (for now) to avoid API churn c) it works reliably

would you be open to this change? Then I might try my hand at a PR and see if this even works.

Issue Analytics

  • State:open
  • Created 5 years ago
  • Reactions:3
  • Comments:8 (4 by maintainers)

github_iconTop GitHub Comments

1reaction
buschtoenscommented, Aug 2, 2018

The core problem was that the Ember observer / computed property system does not watch properties on functions. This means that someTask.performCount would be broken as someTask is a function.

If we changed that in Ember core, then we could do this. 😄

0reactions
buschtoenscommented, Apr 8, 2019

Alright, actually got it working without any hackery:

Edit my-app

import Component from "@glimmer/component";
import { defineProperty, notifyPropertyChange } from "@ember/object";
import { reads } from "@ember/object/computed";
import { timeout } from "ember-concurrency";
import { Task, TaskProperty } from "ember-concurrency/-task-property";
import { TaskGroup } from "ember-concurrency/-task-group";
import { resolveScheduler } from "ember-concurrency/-property-modifiers-mixin";

export default class FooComponent extends Component {
  // This _has_ to be the class field syntax, since the Babel legacy transforms don't support
  // decorating generator methods. 😢
  @task
  someTask = function*() {
    yield timeout(1000);
    return Math.random();
  };
}

function task(Class, taskName, descriptor) {
  return {
    ...descriptor,
    initializer() {
      // @TODO: support concurrency modifiers, like `restartable`, via additional decorators.
      const tp = new TaskProperty();
      tp.maxConcurrency(1);

      const taskFn = descriptor.initializer.call(this);

      // e-c currently requires a new `Task` instance per host object instance. In a future
      // version we would reuse the same task and store host object specific state in a WeakMap.
      const task = Task.create({
        fn: taskFn,
        context: this,
        _origin: this,
        _taskGroupPath: tp._taskGroupPath,
        _scheduler: resolveScheduler(tp, this, TaskGroup),
        _propertyName: taskName,
        _debug: tp._debug,
        _hasEnabledEvents: tp._hasEnabledEvents
      });

      const perform = (...args) => {
        const ti = task.perform(...args);

        // Since the e-c code accesses these properties indirectly and not through the host
        // object, the tracking system does not pick up on the changes. By acessing the props
        // via the host object and sending updates, we can work around that.
        // @see https://github.com/machty/ember-concurrency/blob/62c942d7f41e1555ab835ba921f1293f0c8d1984/addon/-scheduler.js#L95-L97
        notifyPropertyChange(this[taskName], "numRunning");
        notifyPropertyChange(this[taskName], "numQueued");
        ti._onFinalize(() => {
          notifyPropertyChange(this[taskName], "numRunning");
          notifyPropertyChange(this[taskName], "numQueued");
        });

        return ti;
      };
      Object.setPrototypeOf(perform, task);

      return perform;
    }
  };
}
Read more comments on GitHub >

github_iconTop Results From Across the Web

How to remove text from a string? - javascript - Stack Overflow
PS: The replace function returns a new string and leaves the original string unchanged, so use the function return value after the replace()...
Read more >
The Fastest Way to Remove a Specific Item from an Array in ...
One way to solve this problem is using Array.prototype.indexOf() to find the index of the value, then Array.prototype.splice() to remove that item: ...
Read more >
Element.remove() - Web APIs - MDN Web Docs
The Element.remove() method removes the element from the DOM. Syntax. remove()
Read more >
Removing JavaScript's “this” keyword makes it a better ...
Removing JavaScript's “this” keyword makes it a better language. Here's why. ... no function constructor, no new , no Object.create() .
Read more >
Lighthouse: Reduce unused JavaScript - GTmetrix
Dead code elimination is the process of removing JavaScript code that isn't used by your current page. This code may contain remnants from...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found