Skip to content

Commit

Permalink
Merge pull request #13 from datavis-tech/support-async-functions
Browse files Browse the repository at this point in the history
Add support for async functions
  • Loading branch information
curran authored Jun 24, 2018
2 parents 92a3a26 + ed22379 commit 0c35577
Show file tree
Hide file tree
Showing 3 changed files with 99 additions and 10 deletions.
77 changes: 70 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# Topologica.js
Minimal library for reactive [dataflow programming](https://en.wikipedia.org/wiki/Dataflow_programming).
Small (2 kB minified) library for [reactive](https://en.wikipedia.org/wiki/Reactive_programming) [dataflow programming](https://en.wikipedia.org/wiki/Dataflow_programming).

This library provides an abstraction for **reactive data flows**. This means you can define functions in terms of their inputs (dependencies) and outputs, and the library will take care of executing _only_ the required functions to propagate changes through the data flow graph, in the correct order. The ordering of change propagation through the data flow graph is determined using the [topological sorting algorithm](https://en.wikipedia.org/wiki/Topological_sorting) (hence the name _Topologica_).

Expand Down Expand Up @@ -34,21 +34,78 @@ You can also include the library in a script tag from Unpkg, like this:
</script>
```

The library weighs 1.8 kB minified.

## Examples

* [Color Picker Example](https://datavis.tech/edit/09fb48921c454e90aa74d72fbe2eb8a0) - Pick a color with 3 sliders.

## Usage

You can define _reactive functions_ that depend on other properties as inputs.
You can define _reactive functions_ that compute properties that depend on other properties as input. For example, consider the following example where `b` gets set to `a + 1` whenever `a` changes.

```javascript
const dataFlow = DataFlowGraph({
b: λ(
({a}) => a + 1,
'a'
)
});
dataFlow.set({ a: 2 });
assert.equal(dataFlow.get('b'), 3);
```

<p align="center">
<img src="https://cloud.githubusercontent.com/assets/68416/15453189/89c06740-2029-11e6-940b-58207a1492ca.png">
<br>
When a changes, b gets updated.
</p>

Here's an example that assigns `b = a + 1` and `c = b + 1`.

```javascript
const dataFlow = DataFlowGraph({
b: λ(({a}) => a + 1, 'a'),
c: λ(({b}) => b + 1, 'b')
});
dataFlow.set({ a: 5 });
assert.equal(dataFlow.get('c'), 7);
```

<p align="center">
<img src="https://cloud.githubusercontent.com/assets/68416/15385597/44a10522-1dc0-11e6-9054-2150f851db46.png">
<br>
Here, b is both an output and an input.
</p>

Here's an example that uses an asynchronous function.

```javascript
const dataFlow = DataFlowGraph({
b: λ(
async ({a}) => await Promise.resolve(a + 5),
'a'
),
c: λ(
({b}) => assert.equal(b, 10),
'b'
)
});
dataFlow.set({ a: 5 });
```

<p align="center">
<img src="https://user-images.githubusercontent.com/68416/41818527-7e41eba6-77ce-11e8-898a-9f85de1563ed.png">
<br>
Asynchronous functions cut the dependency graph.
</p>

Here's an example that computes a person's full name from their first name and and last name.

```js
const { DataFlowGraph, ReactiveFunction: λ } = Topologica;
const dataFlow = DataFlowGraph({
fullName: λ( // The symbol λ is an alias for Topologica.ReactiveFunction.
({firstName, lastName}) => `${firstName} ${lastName}`, // The first argument is the function, accepting an object.
'firstName, lastName' // The second argument is a comma delimted list of inputs.
fullName: λ(
({firstName, lastName}) => `${firstName} ${lastName}`,
'firstName, lastName'
)
});

Expand All @@ -67,6 +124,12 @@ dataFlow.set({ firstName: 'Wilma' });
assert.equal(dataFlow.get('fullName'), 'Wilma Flintstone');
```

<p align="center">
<img src="https://cloud.githubusercontent.com/assets/68416/15389922/cf3f24dc-1dd6-11e6-92d6-058051b752ea.png">
<br>
The data flow graph for the example code above.
</p>

You can use reactive functions to trigger code with side effects, like DOM manipulation:

```js
Expand Down
13 changes: 10 additions & 3 deletions src/dataFlowGraph.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import Graph from './graph'

const isAsync = fn => fn && fn.constructor.name === 'AsyncFunction';

export const DataFlowGraph = options => {
const values = new Map();
const changed = new Set();
Expand Down Expand Up @@ -35,13 +37,18 @@ export const DataFlowGraph = options => {
Object.entries(options).forEach(entry => {
const [ property, { fn, inputs } ] = entry;

const propertySync = isAsync(fn) ? property + "'" : property;

inputs.forEach(input => {
graph.addEdge(input, property);
graph.addEdge(input, propertySync);
});

functions.set(property, () => {
functions.set(propertySync, () => {
if (allDefined(inputs)) {
values.set(property, fn(getAll(inputs)));
const output = fn(getAll(inputs));
isAsync(fn)
? output.then(value => set({[property]: value}))
: values.set(property, output);
}
});
});
Expand Down
19 changes: 19 additions & 0 deletions test/test.js
Original file line number Diff line number Diff line change
Expand Up @@ -203,4 +203,23 @@ describe('Topologica.js', () => {
});
assert.equal(dataFlow.get('b'), true);
});

it('Should work with async functions.', done => {
const dataFlow = DataFlowGraph({
b: λ(
async ({a}) => await Promise.resolve(a + 5),
'a'
),
c: λ(
({b}) => {
assert.equal(b, 10);
done();
},
'b'
)
});
dataFlow.set({
a: 5
});
});
});

0 comments on commit 0c35577

Please sign in to comment.