Skip to content

Instantly share code, notes, and snippets.

@pixelhandler
Created January 15, 2019 00:11
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save pixelhandler/d773fb3cfcf1a345014d75e43da65b77 to your computer and use it in GitHub Desktop.
Save pixelhandler/d773fb3cfcf1a345014d75e43da65b77 to your computer and use it in GitHub Desktop.
JavaScript Generators and Concurrency
# JavaScript Generators and Concurrency
1. Why generators are a nice fit for handling a coroutine (source/sink)
2. How we use generators in the Dashboard app
3. What generators can do to help wrangle async behavior
Generators are functions that can be paused and resumed (think cooperative multitasking or coroutines)
Pitch:
Generators provide core behavior for an abstraction we rely on for wrangling asynchronous tasks
- Used for data loading, async button, tracking state (last successful), etc.
## Why generators?
- async/await based on generators
- building upon control flow structures parallel to those of generators, and using promises for the return type, instead of defining custom mechanisms.
- using `yield` makes code look synchronous like async/await
- similar to a state machine, keeps track of its own state, but is an iterator - it remembers the progress of its execution
- generators can make using Promises simpler
## What
Generators can play three roles:
- Iterators (data producers): Each yield can return a value via next()
- Observers (data consumers): yield can also receive a value from next()
- Coroutines (data producers and consumers)
## How
- Polling, track an error that happen when online, send track'g event when online again
- Loading, intent to load, but then changing routes; or improving perceived performance, yeah more spinners, but no empty screen
- Async button, element is aware of it's running status or failed result
- Tasks that are droppable, queueable, restartable
- Tasks that are aware of object lifecycle, safe to run even if will be destroyed in the meanwhile
# Code Examples
- Syntaxes expecting iterables
- Counter example
- Polling example, run tracking task when online again
- Polling for printers
- Conditional rendering based on task state
Generators and Iterable
* https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Iterators_and_Generators#Iterables
Syntaxes expecting iterables
```js
for (let value of ['a', 'b', 'c']) {
console.log(value);
}
// "a"
// "b"
// "c"
[...'abc']; // ["a", "b", "c"]
function* gen() {
yield* ['a', 'b', 'c'];
}
gen().next(); // { value: "a", done: false }
[a, b, c] = new Set(['a', 'b', 'c']);
a; // "a"
```
Nonblocking counter
- https://rauschma.github.io/generator-examples/nonblocking-counter/
`this` in generators, the context of the function definition (outer scope the object the *method belongs to )
```js
function* countUp(start = 0) {
while (true) {
start++;
yield* displayCounter(start);
}
}
function* displayCounter(counter) {
const counterSpan = document.querySelector('#counter');
counterSpan.textContent = String(counter);
yield; // pause
}
function run(generatorObject) {
if (!generatorObject.next().done) {
// Add a new task to the event queue
setTimeout(function () {
run(generatorObject);
}, 1000);
}
}
run(countUp());
```
Polling to track event when online again
```js
runWhenOnline(fn) {
return this._runWhenOnlineTask.perform(fn);
},
_runWhenOnlineTask: task(function*(fn) {
while (this.isOffline) {
yield timeout(zft(2000));
}
if (typeof fn === 'function') {
let result = yield fn();
return result;
}
}).enqueue()
trackEvent(event, properties = {}, options = {}) {
let compactedProperties = pickBy(properties, isPresent);
if (window && window.analytics && this.notABoss) {
options = this._setContext(options);
this.online.runWhenOnline(() => {
window.analytics.track(event, compactedProperties, options);
});
}
},
```
Polling for printers
```js
export default Route.extend({
state: service(),
model() {
return { printers: this.store.query('printer', { filter: { location: locationId }, include: 'devices,location' }) };
},
afterModel(model) {
this.pollPrinterTask.perform(model);
},
deactivate() {
get(this.controller, 'listenForBtPairingTask').cancelAll();
},
pollPrinterTask: task(function*(model) {
let maxPollingCountFoTesting = 2;
do {
yield timeout(config.testLongPolling ? 10 : zft(POLL_TIMEOUT));
let printers = yield this.store.query('printer', {
filter: { location: get(this, 'currentLocation.id') }, include: 'location.devices'
});
set(model, 'printers', printers);
maxPollingCountFoTesting--;
} while (config.environment !== 'test' || (config.testLongPolling && maxPollingCountFoTesting));
}).cancelOn('deactivate').restartable()
});
```
Conditional rendering based on task state
```hbs
{{employees/employee-config-options
loadIntegrationsIsLoading=currentLocation.loadIntegrations.isRunning
...
}}
<div class="px3 pb2 border-bottom flex">
{{#if (not loadIntegrationsIsLoading)}}
{{#if canExportEmployees}}
{{#ui-button
data-test-export-button=true
onclick=(action exportEmployees)
as |task|
}}
{{if task.isRunning "Exporting" "Export"}}
{{/ui-button}}
{{/if}}
{{/if}}
</div>
```
Ember Concurrency
...is an Ember Addon that makes it easy to write concise, robust, and beautiful asynchronous code
- http://ember-concurrency.com/docs/introduction/
- http://machty.s3.amazonaws.com/ec-prezo-2016/index.html#/?slide=enter-ember-concurrency-2
- http://ember-concurrency.com/docs/task-function-syntax
- http://ember-concurrency.com/docs/task-concurrency
- http://ember-concurrency.com/docs/cancelation
- http://ember-concurrency.com/docs/derived-state
- https://embermap.com/topics/ember-concurrency/what-is-concurrency
Gotcha of loading data with tasks
- http://ember-concurrency.com/docs/task-cancelation-help
```js
import Component from '@ember/component';
import { task, timeout, didCancel } from 'ember-concurrency';
export default Component.extend({
queryServer: task(function * () {
yield timeout(10000);
return 123;
}),
actions: {
fetchResults() {
this.get('doStuff').perform().then((results) => {
this.set('results', results);
}).catch((e) => {
if (!didCancel(e)) {
// re-throw the non-cancelation error
throw e;
}
});
}
}
});
```
Autocomplete
- http://ember-concurrency.com/docs/examples/autocomplete
## Links
- https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Iterators_and_Generators
- https://caniuse.com/#feat=es6-generators
- https://davidwalsh.name/es6-generators
- http://exploringjs.com/es6/ch_generators.html
- https://codeburst.io/understanding-generators-in-es6-javascript-with-examples-6728834016d5
- https://tc39.github.io/ecmascript-asyncawait/ aside promises, generators pave a pattern for async/await
- https://medium.com/front-end-hacking/modern-javascript-and-asynchronous-programming-generators-yield-vs-async-await-550275cbe433
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment