Giter VIP home page Giter VIP logo

ember-concurrency's People

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  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

ember-concurrency's Issues

Scheduler doesn't match async-await timing

At present, all perform()s are deferred to the actions queue, which occasionally produces different results from putting the same code in an Ember.on() function, e.g.

export default Ember.Component.extend({
  afterInit: Ember.on('init', function() {
    // runs synchronously during/after `init`
    this.set('wat', 'lol');
  }),
  // vs
  afterInitTask: task(function * () {
    // happens later, on the `actions` queue
    this.set('wat', 'lol');
  }).on('init'),
});

I noticed this was causing undesired animations to occur with Liquid Fire which were fixed by changing the code back to user Ember.on('init'). I don't know what the solution is now, but given that most libraries assume that logic in hooks is performed synchronously when the hook is called, ember-concurrency should probably make it possible to perform logic synchronously. Or perhaps e-c can maintain a microqueue that it trampoline-flushes every time .perform() is called.

Errors in Phantom JS 2.0.1 with 0.7.7

I'm attempting an upgrade to 0.7.7 so that I no longer need to include the babel polyfill. I have a number of errors when running tests with Phantom 2.0.1 though:

not ok 478 PhantomJS 2.0 - Unit | Service | membership freshness watcher: stops polling if a server responds with an error
    ---
        actual: >
            null
        stack: >
                at defaultSubject (http://localhost:7000/assets/test-support.js:14287:28)
                at http://localhost:7000/assets/test-support.js:14319:41
                at http://localhost:7000/assets/tests.js:12665:31
                at wrapper (http://localhost:7000/assets/test-support.js:13086:34)
                at runTest (http://localhost:7000/assets/test-support.js:2796:32)
                at run (http://localhost:7000/assets/test-support.js:2781:11)
                at http://localhost:7000/assets/test-support.js:2923:14
                at process (http://localhost:7000/assets/test-support.js:2582:24)
                at begin (http://localhost:7000/assets/test-support.js:2564:9)
                at http://localhost:7000/assets/test-support.js:2624:9
        message: >
            Died on test #1     at testWrapper (http://localhost:7000/assets/test-support.js:13115:16)
                at test (http://localhost:7000/assets/test-support.js:13128:44)
                at http://localhost:7000/assets/tests.js:12641:24
                at exports (http://localhost:7000/assets/vendor.js:127:37)
                at requireModule (http://localhost:7000/assets/vendor.js:30:25)
                at require (http://localhost:7000/assets/test-loader.js:67:16)
                at loadModules (http://localhost:7000/assets/test-loader.js:58:25)
                at load (http://localhost:7000/assets/test-loader.js:89:35)
                at http://localhost:7000/assets/test-support.js:6477:20: undefined is not a constructor (evaluating 'factory.create(options)')

Additionally:

not ok 477 PhantomJS 2.0 - Unit | Service | membership freshness watcher: polls for new content
    ---
        actual: >
            null
        stack: >
                at http://localhost:7000/assets/student-app.js:9824:39
                at exports (http://localhost:7000/assets/vendor.js:127:37)
                at requireModule (http://localhost:7000/assets/vendor.js:30:25)
                at _extractDefaultExport (http://localhost:7000/assets/vendor.js:168998:27)
                at resolveOther (http://localhost:7000/assets/vendor.js:168733:53)
                at superWrapper (http://localhost:7000/assets/vendor.js:33912:26)
                at resolve (http://localhost:7000/assets/vendor.js:16672:47)
                at _setupIsolatedContainer (http://localhost:7000/assets/test-support.js:14387:64)
                at setupContainer (http://localhost:7000/assets/test-support.js:14222:37)
                at http://localhost:7000/assets/test-support.js:13294:30
                at initializePromise (http://localhost:7000/assets/vendor.js:64658:15)
                at Promise (http://localhost:7000/assets/vendor.js:66510:38)
                at nextStep (http://localhost:7000/assets/test-support.js:13293:52)
                at invokeSteps (http://localhost:7000/assets/test-support.js:13300:22)
                at setup (http://localhost:7000/assets/test-support.js:13250:30)
                at setup (http://localhost:7000/assets/test-support.js:13055:28)
                at callHook (http://localhost:7000/assets/test-support.js:2822:24)
                at runHook (http://localhost:7000/assets/test-support.js:2815:13)
                at process (http://localhost:7000/assets/test-support.js:2582:24)
                at begin (http://localhost:7000/assets/test-support.js:2564:9)
                at http://localhost:7000/assets/test-support.js:2624:9
        message: >
            Promise rejected before polls for new content: undefined is not a function (evaluating '(0, _emberConcurrency.task)')
        Log: |

These tests pass in Chrome Canary (53.0.2767.0 canary (64-bit), and Firefox 49.

performing via onsubmit not working?

Is it just me or is performing a task via an onsubmit on a form not working? But an action that then calls perform works.

Does not work:

<form onsubmit={{perform createApp}}>

Works:

actions: {

  createApp: function() {
    this.get('createApp').perform();
  }

}
<form {{action "createApp" on="submit"}}>

RFC: add limiter task modifier

Basically this. I'm currently using it to send x number of requests per y milliseconds. It works but since it uses setTimeout, it doesn't block the ember test helpers for waiting (emberjs/ember.js#3008). I wonder if it was natively in ember-concurrency, we could get this both testable the benefits of your canceling?

(route-task) option?

hey @machty impressive addon. An example of passing in a task directly to a component like you did here would be great to have in the docs. (Happy to help with that) Also, I was wondering if it would be possible to define a task in a route then be able to pass that down to child components. Essentially a e-c version of route-action... (route-task) perhaps?

I haven't tried this but could it be possible in the short term to define the tasks in a route then have actions that simply return the raw task. This way one could use route-action closures that simply point to a route defined task?

allSettled task aware yieldable

Right now you have an "all" yieldable helper you're exporting that takes advantage of the taskAwareVariantOf method. Would it make sense to add a allSettled yieldable so we can handle the errors after all promises have completed? If so I could add a quick pull request to add it.

Issue with perform helper on rerouting to a page. (Ember 2.3.0)

We're seeing this when navigating away from controller using a task and then back again. The first time the page loads everything runs fine, but when we move away and navigate back, we get this in the console:

ember.debug.js:6254 Uncaught Error: Assertion Failed: The first argument passed to the perform helper should be a Task object (without quotes); you passed undefined

Is this a known issue? Race condition on task loading?

RFC: Rename Task/TaskInstance

In an effort to make things easier to talk about and teach I propose a couple renamings:

TaskInstanceTask
TaskTaskManager

The idea is that a "task" should be something with a definite ending, in that task.isFinished makes sense. isFinished wouldn't make sense on a task manager, because it will always be able to spawn more tasks in the future. That brings me to another point. It may be worth exploring spawn as an alternatives to perform to make it clearer that something is being created (a task). This could have an added benefit of giving encapsulated tasks more spotlight.

Conversationally, I think it will still be fine to continue referring to a task manager as a "task", especially in cases where there task manager only allows one concurrent task at a time.

add (task) helper to pass in curried tasks

Presently the perform template helper essentially produces a closure action so that you can pass a task (w curried args) into a component and have it be invoked upon sendAction or this.attrs.actionName(). It seems desirable to be able to pass in a Task with curried args so that there's feature parity w closure actions, and components maintain the ability to stylize themselves according to the isRunning state of the task.

Acceptance tests fail because of TaskCancelation

testem.js:775 TaskCancelation: TaskCancelation
    at Class.cancel (http://localhost:7357/assets/vendor.js:198319:19)
    at Class.spliceTaskInstances (http://localhost:7357/assets/vendor.js:197740:22)
    at Class.cancelAll (http://localhost:7357/assets/vendor.js:197732:12)
    at Class.cancelAll (http://localhost:7357/assets/vendor.js:199072:23)
    at Array.<anonymous> (http://localhost:7357/assets/vendor.js:199463:32)
    at Class.owner.willDestroy (http://localhost:7357/assets/vendor.js:199454:25)
    at invoke (http://localhost:7357/assets/vendor.js:14821:16)
    at Object.flush (http://localhost:7357/assets/vendor.js:14885:11)
    at Object.flush (http://localhost:7357/assets/vendor.js:14693:17)
    at Object.end (http://localhost:7357/assets/vendor.js:15048:25)

Not sure why it's happening..
I only have one task, inside of a service.

Overriding a parent/mixin object's task and then calling it with `this._super.perform()` doesn't work

I have a mixin with a task and I wanted to over-ride/extend it in a consuming service like this:

//some-mixin
Ember.mixin.create({
  sometask: task(function*(stuff){
    let thing = yield someAsyncFunc();
    return thing;
  })
})

//service
Ember.Service.extend(SomeMixin, {
  sometask: task(function * (stuff){
    let thing = yield this._super.perform(stuff);
    //do other async stuff here
  })
})

Wasn't sure if this would work and it doesn't - I get this error :
<snip>Ember.Object.create no longer supports defining methods that call _super.</snip>

So for right now I guess I'll have to re-implement the whole task in the service that is pulling in the mixin. But I think this behavior would be nice-to-have...just not sure if its possible.

Uncaught ReferenceError: regeneratorRuntime is not defined

I just installed via ember install ember-concurrency and followed the instructions on setting my brocolli file to includePolyfills. Upon restarting the ember serve, I get the error "Uncaught ReferenceError: regeneratorRuntime is not defined".

I have no clue what to from here.

Information

~/$ node -v
~/$ v5.0.0

package.json

"devDependencies": {
    "broccoli-asset-rev": "^2.4.2",
    "broccoli-sass": "0.7.0",
    "ember-ajax": "0.7.1",
    "ember-cli": "2.5.0",
    "ember-cli-app-version": "^1.0.0",
    "ember-cli-babel": "^5.1.6",
    "ember-cli-dependency-checker": "^1.2.0",
    "ember-cli-htmlbars": "^1.0.3",
    "ember-cli-htmlbars-inline-precompile": "^0.3.1",
    "ember-cli-inject-live-reload": "^1.4.0",
    "ember-cli-jshint": "^1.0.0",
    "ember-cli-qunit": "^1.4.0",
    "ember-cli-release": "0.2.8",
    "ember-cli-sri": "^2.1.0",
    "ember-cli-uglify": "^1.2.0",
    "ember-concurrency": "0.7.0",
    "ember-data": "^2.5.0",
    "ember-export-application-global": "^1.0.5",
    "ember-load-initializers": "^0.5.1",
    "ember-resolver": "^2.0.3",
    "loader.js": "^4.0.1"
  }

Additionally
I just created this project fresh from ember new app

Task properties cannot be overridden with mocks at object instantiation

After the changes discussed in #81 , I've also noticed that passing a mock of a task property at object instantiation silently fails to override the prototype property, presumably due to the set no-op. Mocking is less of a core use case than classical inheritance, but the library may still want to preserve conventional behavior, or emit a descriptive warning if this behavior can't be preserved alongside the accessor property added in 0.7.11. Here's a test case:

test("tasks can be overridden at object instantiation", function(assert) {
  assert.expect(1);

  let Obj = Ember.Object.extend({
    myTask: task(function * () {
      throw new Error("shouldn't get here");
    }),
  });

  let objInstance = Obj.create({
    myTask: {
      perform() {
        assert.ok(true);
      }
    },
  });

  Ember.run(() => {
    objInstance.myTask.perform();
  });
});

I'll try to find some time to dig into possible solutions here, but it's unlikely I'll take a closer look until the weekend.

`.maxConcurrency()` on its own should throw an error

If you just do task(...).maxConcurrency(n) without applying any other modifier, we should probably raise an error and tell you to use one of the other task modifiers for clarity. This would be a "breaking" change but it would nudge you to clarify intent, whereas by default today .maxConcurrency() on its own behaves the same as .enqueue().maxConcurrency(n), which has caused some confusion because some people think it implies .drop(), others .restartable(), etc.

TaskCancelation => TaskCancellation?

This feels like such a troll issue to open...so my apologies if it is.

It looks like, by some of your examples, that if we're putting a try/catch in our tasks, we can check for e.name === 'TaskCancelation' to determine if it was an error in the task vs. external cancellation for some other reason (maxConcurrency, manually canceled, etc).

But, should it be TaskCancelation or TaskCancellation? (link to dictionary.com which I had to use to check the spelling because I wasn't sure)

I can open a PR to fix if you'd like — but, but because of the potential breaking change nature of it, I figured I'd do a "wait and see".

Errors from subtasks are re-thrown even when caught

I believe @wecc mentioned seeing something like this in Slack as well, but I didn't see an issue already filed here. If I perform task below, the resulting error hits the catch block, but it also ends up being rethrown elsewhere and trickling up to window.onerror.

task: task(function* () {
  try {
    yield this.get('subtask').perform();
  } catch (error) {
    console.log('caught', error);
  }
}),

subtask: task(function* () {
  throw new Error('boom');
})

image

Error When Using in Component Integration Test

what I'd expect to work:

it('should support paginated ember-concurrency tasks', function() {
    this.set('itemsTask', task(function * (result) {
      return yield Ember.RSVP.resolve(result);
    }));

    page.render(hbs`{{collection-count task=itemsTask}}`);

    expect(page, 'page').to.have.deep.property('count.isVisible', false);

    const items = [ 1, 2, 3, 4 ];
    items.meta = { pagination: { total: 1234, limit: 20, offset: 50 } };
    this.get('itemsTask').perform(items);

    return wait()
      .then(() => {
        expect(page, 'page').to.have.deep.property('count.text', 'Showing 51 - 70 of 1234');

        const items = [ 1, 2, 3, 4 ];
        items.meta = { pagination: { total: 1234, limit: 20, offset: 70 } };
        this.get('itemsTask').perform(items);

        return wait();
      })
      .then(() => {
        expect(page, 'page').to.have.deep.property('count.text', 'Showing 71 - 90 of 1234');
      });
  });

the error thrown:

Cannot read property '__ember_processes_destroyers__' of undefined
        TypeError: Cannot read property '__ember_processes_destroyers__' of undefined
            at _cleanupOnDestroy (http://localhost:7357/assets/vendor.js:84575:27)
            at init (http://localhost:7357/assets/vendor.js:83744:52)
            at superWrapper [as init] (http://localhost:7357/assets/vendor.js:37626:22)
            at new Class (http://localhost:7357/assets/vendor.js:51358:14)
            at Function.ClassMixinProps.create (http://localhost:7357/assets/vendor.js:51629:14)
            at Object.<anonymous> (http://localhost:7357/assets/vendor.js:83969:19)
            at TaskProperty.ComputedPropertyPrototype.get (http://localhost:7357/assets/vendor.js:29731:28)
            at Object.get (http://localhost:7357/assets/vendor.js:35331:19)
            at Object.exports.default._emberMetalStreamsStream.default.extend.compute (http://localhost:7357
/assets/vendor.js:36428:40)
            at Object.BasicStream.value (http://localhost:7357/assets/vendor.js:36621:27)

which seems to trace to

if (!owner.willDestroy.__ember_processes_destroyers__) {

what works:

it('should support paginated ember-concurrency tasks', function() {
    this.setProperties({
      willDestroy() {},
      itemsTask: task(function * (result) {
        return yield Ember.RSVP.resolve(result);
      })
    });

    page.render(hbs`{{collection-count task=itemsTask}}`);

    expect(page, 'page').to.have.deep.property('count.isVisible', false);

    const items = [ 1, 2, 3, 4 ];
    items.meta = { pagination: { total: 1234, limit: 20, offset: 50 } };
    this.get('itemsTask').perform(items);

    return wait()
      .then(() => {
        expect(page, 'page').to.have.deep.property('count.text', 'Showing 51 - 70 of 1234');

        const items = [ 1, 2, 3, 4 ];
        items.meta = { pagination: { total: 1234, limit: 20, offset: 70 } };
        this.get('itemsTask').perform(items);

        return wait();
      })
      .then(() => {
        expect(page, 'page').to.have.deep.property('count.text', 'Showing 71 - 90 of 1234');
      });
  });

notice the addition of a fake willDestroy method on line 3.

I'm guessing the Ember integration test context doesn't have a willDestroy method, which is why the fake is needed. Is there a way ember-concurrency can detect that and not hang __ember_processes_destroyers__ off of it?

perform deprecation warning can be triggered outside a template

e.g.

myTask = task(function*(){...}),
_doSomething: Ember.on('didInsertElement', this.get('myTask').perform)

Not sure if there's another way to check whether one is in a template besides checking the context, though.
Also the above can be easily circumvented with a function definition or a bind..

Just thought I'd mention this..

willDestroy override prevents unit testing component functionality with mixins

example of issue
failing tests

I added ember concurrency to a component I have that includes ember-keyboard to allow users to use hotkeys for speed.

There are a few methods in this class that I need to unit test. Most all of my tests for this component are integration tests, but there is some logic dealing with scrolling that is not suitable for integration tests, and I need to use a unit test to mock and test this behavior.

By adding any task, I start to get this error when the unit test is tearing down the container:

Assertion Failed: Attempting to lookup an injected property on an object without a container, ensure that the object was instantiated via a container.

I tracked it down to the _cleanupOnDestroy method in ember concurrency.

export function _cleanupOnDestroy(owner, object, cleanupMethodName) {

It seems that the way it's applying the super willDestroys causes this error, as the ember-keyboard mixin implements a willDestroy and it's called during teardown of the test.

Aside from not using unit tests at all for components (I think there's an argument for some limited cases where their use is necessary), is there any kind of workaround to make this work?

Problem using it inside Ember Power Select

Hi,

This is not really a problem but maybe I can get some guidance. I am refactoring Ember Power Select to use ember-concurrency internally and I've found a weird problem. Just installing ember-concurrency and restarting the server results in

app.import is not a function

I'm sure there is a problem specific to EPS, since installing it in a fresh addon works ok.
The only place in the addon where I'm using app.import is in the index.js, but I don't see any red light there.

Ideas?

Loading UI + transitions

So I have a login-panel component that has a loginTask. When a button in the login-panel component is clicked, the loginTask is "performed" and the login-panel's template makes use of ember-concurrency's isIdle derived state to display a loading indicator. Here's an example of what I mean:

{{!-- app/components/login-panel/template.hbs --}}
Login Panel Example

<button type="button" onclick={{perform loginTask}}>
  {{#if loginTask.isIdle}}
    Sign in
  {{else}}
    <i class="fa fa-cog fa-spin fa-lg"></i> Signing in...
  {{/if}}
</button>
// app/components/login-panel/component.js
export default Ember.Component.extend({
  login: null, // pass in a route/controller action  

  loginTask: task(function* () {
    yield this.get('login')()
  })
})

In this case, the login action comes from a route:

export default Ember.Route.extend({
  actions: {
    login() {
      return this.get('session').open(/* ... */).then(() => {
        this.transitionTo('index')
      })
    }
  }
})

With the code above, the loading indicator (icon and Signing in text) is swapped with the idle text (Sign in) too early. As a workaround, I have to add a yield timeout(5000) to the loginTask so the loading indicator remains until the template is swapped with the index route's template.

So this seems kinda hacky...is this a correct usage of ember-concurrency? I've tried fiddling with the .promise property on the Transition object, but it seems to reject with a TransitionAborted error.

In any case, thanks in advance! This addon is very useful. 👍

Common Patterns Discussion

We have a few common patterns we notice happening and wanted to bring them up in a discussion to see if its anything you think would be helpful adding in as a default operation.

1. Unwrapping promises passed in

Most of our tasks start with something like:

someTask: task(function * (one, two, three) {
  one = yield one;
  two = yield two;
  three = yield three;

Since we are working with promises or previously fulfilled promises, we want to unwrap them so we have access to the methods not just through the proxy object on an ember data promise. It would be great to have all the parameters already unwrapped. Any thoughts or ideas on this?

2. Instance based isRunning task property

Since tasks live on an object and once that object dies, all the tasks die. It would be great to have something similar a global property that checks if any task on that object is running. We usually break up code into logical tasks methods, but we create a property like this on the object to see if any task is running.

isRunning: Ember.computed('taskOne.isRunning', taskTwo.isRunning', taskThree.isRunning', ...

That way if any task is running on that object the UI can reflect a processing state. Maybe there is a better alternative you've already found.

Thanks for ember concurrency, it has totally changed how we structure our ember apps.

[example request] Retrieving a value from a checkbox

<input type='checkbox' onchange={{action 'save' value='target.value'}}>

This value='target.value' part magically passes the checkbox's checked as an argument to the action. I've tried playing with it, any attempt of fiddling with this action's arguments ruins the magic.

If I replace the action with perform, how do I retrieve the checkbox's current value?

Note that the value isn't bound.

Also note, this cannot be replaced with {{input}} unless you subclass it, because it doesn't provide an onchange hook, and overriding change prevents the component from accessing its raw HTML value.

Cannot reliably catch errors from a task?

Hey. First of all, gotta say, ember-concurrency is awesome. Thanks to everyone involved for the work on this great library. But I have a little issue that I'm not sure how I should handle.

I have a pretty simple task; keep reloading an Ember Data model until its 'state' property is 'complete'.

reloadUntilComplete: task(function * (reportExecution) {
  while (reportExecution.get('state') !== 'complete') {
    yield reportExecution.reload();
    yield timeout(1000);
  }
}),

And I'm invoking the task as follows:

const task = service.get('reloadUntilComplete').perform(someObject);
task.catch((err) => console.log('error occurred:', err));

But in the event that the first execution of 'reload' throws an error or returns a rejected promise, my 'catch' handler is never called, and instead I have an "Uncaught error" message logged. The reason for this seems to be that the task is invoked immediately on the perform, has the error occur, and sends it to the promise's error handler... but the 'task.catch()' method has not yet been invoked. Therefore it shows up as an uncaught error from RSVP's point-of-view.

I've worked around this issue by adding a yield timeout(0); to the beginning of my task; this delays the execution of actual logic until the next run-loop and allows my handler to attach. But, this seems to be a fairly general issue that prevents a task's errors from being reliably handled. Is there an approach that I'm missing on how to handle this?

[example request] Using with async button

ember-async-button is a super handy component that gives you a link/button with default, pending, failed and success states.

It should naturally integrate with ember-concurrency. I wonder if it's already possible to use the two with zero boilerplate or there must be some changes made to ember-async-button to minimize glue code?

Task overriding broken in 0.7.11

In 0.7.10, overriding a task definition in an extend call worked as I'd expect a normal property override to, but with the getter changes in 0.7.11 it now throws TypeError: Cannot set property go of [object Object] which has only a getter.

const MyThing = Ember.Object.extend({
  go: task(function*() { return 'yes'; })
});

const MyOtherThing = MyThing.extend({
  go: task(function*() { return 'actually, no'; })
});

Task concurrency not updating when yielding an Ember Data promise

I'm trying to use a task's isRunning property to show a spinner while a task is pending. (isRunning is computed based on the task's concurrency count.)

When I yield an ember-concurrency timeout(), or a promise that uses setTimeout(), the spinner behaves as expected. However, when I yield an Ember Data query (a promise), there is a delay before the spinner shows.

A strange workaround: If I put an additional yield timeout(0); before the actual promise I'm waiting for, the spinner spins as expected. (It doesn't matter what number I pass to timeout().)

(I'm using mirage to fake the API responses, if that comes into play. The spinner does not spin while mirage's handlers are executing.)

Component template:

<button {{action 'submit'}}>
  {{#if submitTask.isRunning}}
    Wait... <img src="spinner.gif">
  {{else}}
    Submit
  {{/if}}
</button>

Works

This version of the component shows the spinner while the promise is not yet resolved:

export default Ember.Component.extend({
  actions: {                                           
    submit() {                            
      this.get('submitTask').perform();
    }
  },

  submitTask: task(function * () {      
    yield timeout(1000);
  })                                           
});

This also shows the spinner:

export default Ember.Component.extend({
  actions: {                                           
    submit() {                            
      this.get('submitTask').perform();
    }
  },

  submitTask: task(function * () {      
    yield new Ember.RSVP.Promise((resolve) => {    
      setTimeout(function() {
        resolve();
      }, 1000);
    });
  })                                           
});

Passing yield timeout(0) as a workaround:

export default Ember.Component.extend({
  actions: {                                           
    submit() {                            
      this.get('submitTask').perform();
    }
  },

  submitTask: task(function * () {      
    yield timeout(0);
    yield this.get('store').query('thing', { whatever });
  })                                           
});

Does not work

This does NOT show the spinner (the query still fires and gets the correct results):

export default Ember.Component.extend({
  actions: {                                           
    submit() {                            
      this.get('submitTask').perform();
    }
  },

  submitTask: task(function * () {      
    yield this.get('store').query('thing', { whatever });
  })                                           
});

This also does NOT show the spinner (the query still fires and gets the correct results):

export default Ember.Component.extend({
  actions: {                                           
    submit() {                            
      this.get('submitTask').perform();
    }
  },

  submitTask: task(function * () {      
    yield new Ember.RSVP.Promise((resolve) => {
      this.get('store').query('thing', { whatever })
        .then(() => resolve());
    });
  })                                           
});

Flesh out .perform()-after-destroy behavior

I don't think we protect weird things from happening if you call .perform() on a task after its host object is destroyed. I'm thinking the behavior should be the same as calling .perform() on a .drop() task that's already running.

/cc @OFbriggs

[example request] Different tasks share common concurrency context

We have a large component that triggers various model.save() and customService.request() operations. Those operations are started from different actions and do different things, but only one action is allowed at once, and UI must be blocked while any of the operations is pending.

RFC Proposal: Expose low-level coroutine functions

Expose low-level coroutine functions

In the same vein of tj/co it would be helpful to use coroutine patterns in places outside of tasks.

Much of ember uses promises. Knowing that promises improve the state of asynchronous code, they also introduce a level of cognitive complexity that is difficult to maintain in many situations. e-c tasks and coroutines are interchangeable with promises.

This is a proposal to offer the same interchange functionality that e-c uses under the hood to manage tasks to the user so they can use them in places where a task would be inappropriate.

Possible use cases (non-exhaustive list)

  • Asynchronous test cases
  • beforeModel, model, and afterModel hooks
  • Custom networking services
  • Asynchronous procedures in build scripts
  • ember generate server code
  • Addon code (scripts used as part of blueprints and/or build hooks)

At the moment if you want to use coroutines in these cases you have to npm install co --save-dev which I think is node specific and not included in the final ember output.

Using other coroutine libraries is duplicating efforts since e-c already implements these under the hood.

Proposed API (A)

import { spawn } from 'ember-concurrency';

let promise1 = spawn(function * () {});  // => Promise
let wrapped = spawn.wrap(function * () {}); // => Function
let promise2 = wrapped();                // => Promise

Proposed API (B)

import { coroutine } from 'ember-concurrency';

let promise1 = coroutine(function * () {});    // => Promise
let wrapped = coroutine.wrap(function * () {}); // => Function
let promise2 = wrapped();                      // => Promise

Examples

test('…', spawn.wrap(function * (assert) {
  yield visit('…');
  yield clickOn('button');
  assert.ok($('button').is(':disabled'));
});
model: spawn.wrap(function * () {
  let data = yield $.ajax({url: '…'});
  let data2 = yield $.ajax({url: '…', data});
  return data2.map(itemData => MyItem.create(itemData));
})
model() {
  let users = spawn(function * () {
    let config = yield $.ajax({url: '/config'});
    return Ember.get(config, 'memberships.users');
  });

  let posts = $.ajax({url: '/posts'});

  return RSVP.hash({users, posts});
}
var spawn = require('ember-concurrency/spawn');

function wrap(gen) {
  let fn = spawn.wrap(gen);
  return function (req, res, next) {
    return fn(req, res, next).catch(next);
  };
}

module.exports = function (app) {
  app.get('/api/post', wrap(function * (req, res) {
    let data = {
      meta: { foo: 'bar' },
      data: yield getDataAsync()
    };
    res.setHeader('Content-Type', 'application.json');
    res.send(JSON.stringify(data));
  }));
};

pausing tasks

I have an array of objects, let's say 100 todos, which i want to delete. The API does not accept an array of objects to delete and so instead I have to issue the delete request one at a time. When the end user issues/starts the delete request, they may want to pause/resume at any point before all the tasks have completed.

Once an AJAX request has been made to the API server to delete an object, I am not able to cancel the request, as the request has been made and is out of the browsers hands. So when the user clicks "cancel", after starting a bulk delete request, what I want to do is

  1. pause only the "waiting" tasks....let the "running" tasks complete
  2. give the user on option to resume/restart those tasks that are manually paused

Any thoughts on a way to achieve this?

Adding a task to an object breaks willDestroy

Repo with a reproducible error: https://github.com/cibernox/bug-in-ember-concurrency

Steps:

  1. Run the app
  2. Visit http://localhost:4200/tests?filter=Acceptance%20|%20Main
  3. Open the console to see the logs

What is wrong

The willDestroy method seems to be called not with the component, but with some subclass of it, that has a different guid and is, in summary, an entirely different object.

If you comment the task in app/components/test-component.js the componen behaves as expected.

Any bisect?

Yes, this works fine in 0.7.10 and wrong in 0.7.11

It should be possible to "background" a .perform

From @SirZach in embercommunity:

i’m hoping to be able to do something like make an ajax request and if it’s taking longer than n period of time, show something to user to let them know that work is still being done. anyone have any thoughts on how to get that going?

I don't think there's actually an idiomatic way to do this atm; the only thing I could think of was:

    import { task, race } from 'ember-concurrency';myTask: task(function * () {
      let displaySoon = this.get('displayMessageSoon', "Still working...");
      try {
        let val = yield $.ajax(...);
        return val;
      } finally {
        displaySoon.cancel();
      }
    }),displayMessageSoon: task(function * (delay, message) {
      yield timeout(delay);
      this.set('status', message);
    }),

But it's pretty gross to have to manually self-cancel... there should probably be something like yield attach(this.get('displayMessageSoon',"still working"))

Improve debugging experience

  • add ability to log why a task was canceled, displaying the names of the chain of cancelation, the root cause, and the name of the policy (where applicable) that caused the cancelation
  • investigate bringing the visuals/graphs from the docs into the console, via background images, or console.table, or something better than a console.log string

Enqueue one task and drop the rest?

I found the next scenario. I have a form that saves on blur on any onchange of it's inputs. In other words, as the user moves between fields, every time an input is blurred to focus the next and there was a change, the model is saved.

If a change occurs while the previous save is still ongoing, I want to enqueue another save, but once I have one enqueued saved, there is no point in enqueuing more, since whenever that task is performed, the save will contain all accumulative changes.

Is there any way of getting a task().enqueueNoMoreThan(N)?

[example request] Basic usage with `ember-ajax`

There are example that uses low-level xhr, but the most common use case (apart from Ember Data) is ember-ajax.

What is the most efficient and compact way of using ember-concurrency with ember-ajax? Is it possible to cancel ember-ajax requests?

Flesh out the difference b/w Ember.on and looper

What's the difference between

let MyObject = Ember.Object.extend({
  myChannel: channel(),
  doStuff: looper('myChannel', function(val) {
    // ??
  }),
});

and

let MyObject = Ember.Object.extend({
  myChannel: channel(),
  doStuff: Ember.on('myChannel', function(val) {
    // ??
  }),
});

Basically, if you're passing a regular function, then they're the same. The only conceptual difference is:

  • if you're using a generator with an Evented event, you lose the semantics that you're the only person handling the event

We could potentially override Ember.on to be generator-aware, but this seems clumsy and error-prone.

All in all it's probably better to keep looper seperate from on, but I'll continue to noodle.

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.