Railway oriented programming in Ember. To install:
ember install ember-pipeline
ember-pipeline
allows you to compose a pipeline of (promise aware) methods on an object using "railway oriented programming". That is, if any of the methods in the pipeline returns a CANCEL
token, the entire pipeline exits and can be optionally handled by another method. If the host Ember.Object
is destroyed, the pipeline is aborted as well.
For example:
import Ember from 'ember';
import { pipeline, step, CANCEL } from 'ember-pipeline';
const { computed, get } = Ember;
export default Component.extend({
fetchStoreLocations: computed(function() {
return pipeline(this, [
step('requestGeolocation'),
step('fetchStoresInProximity'),
step('sortStoresByDistance'),
step('alwaysCancels')
]).onCancel((cancellation) => this.handleCancel(cancellation));
}),
requestGeolocation() { /* ... */ },
fetchStoresInProximity() { /* ... */ },
sortStoresByDistance() { /* ... */ },
alwaysCancels() {
return CANCEL();
},
handleCancel(cancellation) {
switch (cancellation.fnName) {
case 'requestGeolocation':
// show error message saying you didn't allow us to use geo api
break;
case 'fetchStoresInProximity':
// no stores around you, sorry!
break;
case 'sortStoresByDistance':
// we used bubble sort
break;
default:
// no cancel handler
console.log(`last value: ${cancellation.result}, reason: ${cancellation.reason}`);
break;
}
}),
actions: {
fetchStoreLocations(...args) {
return get(this, 'fetchStoreLocations').perform(...args);
}
}
});
First, create a pipeline using pipeline
and step
. You can also define a cancel handler:
return pipeline(this, [
step('step1'),
step('step2'),
step('step3')
]).onCancel((cancellation) => this.handleCancel(cancellation));
If using inside of an Ember.Object
, you could make this a computed property:
export default Component.extend({
myPipeline: computed(function() {
return pipeline(this, [
step('step1'),
step('step2'),
step('step3')
]).onCancel((cancellation) => this.handleCancel(cancellation));
})
});
step
receives either a method name as a string, or a function:
[step('step1'), step(x => x * x)];
In a step
function, return CANCEL()
to abort the pipeline:
{
step1() {
return CANCEL('optional reason, can be any type');
}
}
Then, to run the pipeline, get the reference to it and perform
it:
get(this, 'myPipeline').perform(...args);
pipelineInstance.perform(...args);
You can compose new pipelines at runtime. For example:
export default Component.extend({
makePipeline(steps) {
return pipeline(this, steps)
.onCancel((cancellation) => this.handleCancel(cancellation));
},
// ...
actions: {
normal(...args) {
return this.makePipeline([step('step1'), step('step2')]).perform(...args);
},
reverse(...args) {
return this.makePipeline([step('step2'), step('step1')]).perform(...args);
}
}
});
After a pipeline has been performed, you can get derived state:
get(this, 'myPipeline').perform(1, 2, 3);
get(this, 'myPipeline.successfulSteps.length'); // 2
get(this, 'myPipeline.cancelledSteps.length'); // 1
get(this, 'myPipeline.successfulSteps'); // array of successful Steps
get(this, 'myPipeline.cancelledSteps'); // array of cancelled Steps
Because features are still in flux, detailed API docs are coming soon!
- Support
ember-concurrency
tasks
git clone <repository-url>
this repositorycd ember-pipeline
npm install
bower install
ember serve
- Visit your app at http://localhost:4200.
npm test
(Runsember try:each
to test your addon against multiple Ember versions)ember test
ember test --server
ember build
For more information on using ember-cli, visit https://ember-cli.com/.