Awaiting Events / Conditions

In addition to pausing the execution of a task until a yielded promise resolves, it's also possible to pause the execution of a task until some other event or condition occurs by using the following helpers:

  • waitForEvent Pause the task until an Ember/DOM/jQuery event fires.
  • waitForProperty Pause the task until a property on an Ember object becomes some expected value.
  • waitForQueue Pause the task and resume execution within a specific Ember Run Loop queue.

The patterns described below can be seen as alternatives to using observers and other patterns that are often prone to abuse; proper use of observers or Ember.Evented hooks often requires conditionals and defensive programming to ensure that the app is in some expected state before running the logic in that hook, which is the kind of error-prone busywork that ember-concurrency intends to prevent.

By expressing event-driven code within tasks using the patterns below, you are already heavily constraining the conditions in which that event or observer can fire, which means you don't have to write nearly as much guard/cleanup logic to ensure that an event doesn't fire at an unexpected time/state.

Waiting for Ember & DOM / jQuery Events

You can use waitForEvent(object, eventName) to pause your task until an Ember.Evented or DOM / jQuery Event fires. object must include Ember.Evented (or support .one() and .off()) or be a valid DOM EventTarget (or support .addEventListener() and .removeEventListener()).

This is useful for when you want to dynamically wait for an event to fire within a task, but you don't want to have to set up a Promise that resolves when the event fires.

Example

Try clicking around the page; waitForEvent will install handlers and wait for the specified Ember, DOM or jQuery event to fire; the value returned from yield is the event that was fired.

domEvent: (x=, y=)

jqueryEvent: (x=, y=)

emberEvent: (v=)

domEvent = null;
@task *domEventLoop() {
  while (true) {
    let event = yield waitForEvent(document.body, 'click');
    this.set('domEvent', event);
    this.trigger('fooEvent', { v: Math.random() });
  }
}

jQueryEvent = null;
@task *jQueryEventLoop() {
  let $body = $('body');
  while (true) {
    let event = yield waitForEvent($body, 'click');
    this.set('jQueryEvent', event);
  }
}

emberEvent = null;
@task *emberEventedLoop() {
  while (true) {
    let event = yield waitForEvent(this, 'fooEvent');
    this.set('emberEvent', event);
  }
}

didInsertElement() {
  super.didInsertElement(...arguments);
  this.domEventLoop.perform();
  this.jQueryEventLoop.perform();
  this.emberEventedLoop.perform();
  this.waiterLoop.perform();
}
<h4>
  domEvent: (x={{this.domEvent.offsetX}}, y={{this.domEvent.offsetX}})
</h4>

<h4>
  jqueryEvent: (x={{this.jQueryEvent.offsetX}}, y={{this.jQueryEvent.offsetX}})
</h4>

<h4>
  emberEvent: (v={{this.emberEvent.v}})
</h4>

Example with Derived State

Sometimes, it's desirable to know whether a certain part of a task is executing, rather than the whole task. For instance, in the example below, the waiterLoop task has an infinite loop, so waiterLoop.isRunning will always be true. If you want to know whether a specific part of that loop is running, then you can just extract some of that logic into another task and check if that subtask's isRunning property in the template.

Thanks!

@task *waiterLoop() {
  while (true) {
    yield this.waiter.perform();
    yield timeout(1500);
  }
}

@task *waiter() {
  let event = yield waitForEvent(document.body, 'click');
  return event;
}
<h4>
  {{#if this.waiter.isRunning}}
    Please click somewhere...
  {{else}}
    Thanks!
  {{/if}}
</h4>

Waiting for a condition / property

You can use waitForProperty(object, property, callbackOrValue) to pause your task until a property on an Ember Object becomes a certain value. This can be used in a variety of use cases, including coordination execution between concurrent tasks.

State:
@task *startAll() {
  this.set('bazValue', 1);
  this.set('state', 'Start.');
  this.foo.perform();
  this.bar.perform();
  this.baz.perform();
}

@task *foo() {
  yield timeout(500);
}

@task *bar() {
  yield waitForProperty(this, 'foo.isIdle');
  this.set('state', `${this.state} Foo is idle.`);
  yield timeout(500);
  this.set('bazValue', 42);
  this.set('state', `${this.state} Bar.`);
}

bazValue = 1;
@task *baz() {
  let val = yield waitForProperty(this, 'bazValue', (v) => v % 2 === 0);
  yield timeout(500);
  this.set('state', `${this.state} Baz got even value ${val}.`);
}
<button {{on "click" (perform this.startAll)}} type="button">
  Start
</button>

<h5>
  State: {{this.state}}
</h5>