Created
January 15, 2019 00:11
-
-
Save pixelhandler/d773fb3cfcf1a345014d75e43da65b77 to your computer and use it in GitHub Desktop.
JavaScript Generators and Concurrency
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# 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