Skip to content

Instantly share code, notes, and snippets.

@dsafonov-grid
Last active July 23, 2022 23:58
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save dsafonov-grid/29aa8f11ea2208bd5f4095f7c12bfdcd to your computer and use it in GitHub Desktop.
Save dsafonov-grid/29aa8f11ea2208bd5f4095f7c12bfdcd to your computer and use it in GitHub Desktop.
New Twiddle
import Evented from '@ember/object/evented';
import Component from '@ember/component';
import $ from 'jquery';
import { task, timeout, waitForEvent, waitForProperty } from 'ember-concurrency';
export default class EventsExampleComponent extends Component.extend(Evented) {
// BEGIN-SNIPPET waitForEvent
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();
}
// END-SNIPPET
// BEGIN-SNIPPET waitForEvent-derived-state
@task *waiterLoop() {
while(true) {
yield this.waiter.perform();
yield timeout(1500);
}
}
@task *waiter() {
let event = yield waitForEvent(document.body, 'click');
return event;
}
// END-SNIPPET
// BEGIN-SNIPPET waitForProperty
@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}.`);
}
// END-SNIPPET
}
import Controller from '@ember/controller';
import { waitForProperty, task, timeout } from 'ember-concurrency';
import {action} from '@ember/object';
export default class ApplicationController extends Controller {
@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>
<MyComponent/>
<h2>Awaiting Events / Conditions</h2>
<p>
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:
</p>
<ul>
<li>
<a href="/api/global.html#waitForEvent">waitForEvent</a>
Pause the task until an Ember/DOM/jQuery event fires.
</li>
<li>
<a href="/api/global.html#waitForProperty">waitForProperty</a>
Pause the task until a property on an Ember object becomes
some expected value.
</li>
<li>
<a href="/api/global.html#waitForQueue">waitForQueue</a>
Pause the task and resume execution within a specific
Ember Run Loop queue.
</li>
</ul>
<p>
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.
</p>
<p>
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.
</p>
<h3>Waiting for Ember & DOM / jQuery Events</h3>
<p>
You can use <code><a href="/api/global.html#waitForEvent">waitForEvent(object, eventName)</a></code> to pause your task until
an Ember.Evented or DOM / jQuery Event fires.
<code>object</code> must include Ember.Evented (or support <code>.one()</code>
and <code>.off()</code>) or be a valid DOM EventTarget (or support
<code>.addEventListener()</code> and <code>.removeEventListener()</code>).
</p>
<p>
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.
</p>
<h3>Example</h3>
<p>
Try clicking around the page; <code>waitForEvent</code> will install
handlers and wait for the specified Ember, DOM or jQuery event to fire;
the value returned from <code>yield</code> is the event that was fired.
</p>
{{!BEGIN-SNIPPET waitForEvent}}
<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>
{{!END-SNIPPET}}
<h3>Example with Derived State</h3>
<p>
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 <code>waiterLoop</code> task has an infinite loop, so
<code>waiterLoop.isRunning</code> 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 <code>isRunning</code> property in the template.
</p>
{{!BEGIN-SNIPPET waitForEvent-derived-state}}
<h4>
{{#if this.waiter.isRunning}}
Please click somewhere...
{{else}}
Thanks!
{{/if}}
</h4>
{{!END-SNIPPET}}
<h3>Waiting for a condition / property</h3>
<p>
You can use <code><a href="/api/global.html#waitForProperty">waitForProperty(object, property, callbackOrValue)</a></code>
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.
</p>
{{!BEGIN-SNIPPET waitForProperty}}
<button {{on "click" (perform this.startAll)}} type="button">
Start
</button>
<h5>
State: {{this.state}}
</h5>
{{!END-SNIPPET}}
{
"version": "0.17.1",
"EmberENV": {
"FEATURES": {},
"_TEMPLATE_ONLY_GLIMMER_COMPONENTS": false,
"_APPLICATION_TEMPLATE_WRAPPER": true,
"_JQUERY_INTEGRATION": true
},
"options": {
"use_pods": false,
"enable-testing": false
},
"dependencies": {
"jquery": "https://cdnjs.cloudflare.com/ajax/libs/jquery/3.5.1/jquery.js",
"ember": "3.18.1",
"ember-template-compiler": "3.18.1",
"ember-testing": "3.18.1"
},
"addons": {
"@glimmer/component": "1.0.0",
"ember-concurrency": "2.0.3"
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment