Communicating sequential flows
It is runner, which create asynchronous flow with the javascript es6 generators, native functions and promises.
Docs
Concept
The concept of the library is very close to co, but unlike co and others similar tools, scf can be considered as greedy - it executes each entity, that can be executed.
For example, the function returns another function:
{ return 'ok'} // ok
We got "ok"
, because nested function has executed too.
The same structure with co returns a function, instead of final result:
// function()
Otherwise, each passed to the runner function will be invoked with the parent context.
Example:
{ return thisenv;} { thisenv = 'node' return getEnv;} // node
Payload
When each entity is executed, the question arises how to predict the final result.
And to understand this, there is such a thing as a payload.
Each the returned value will be considered as payload and will be returned as final result if it is not of one the next types:
- Promises
- Functions (also async functions)
- Generators
- Generator functions
const Csf = Csf // 108
Context inheritance
Any native function inherit parent context. This allows you to implicitly transfer data between different flow depth levels.
Even if middling function is a arrow-function, but it returns classic function, context will be proxied to that classic function.
Csf // "object"
Flow control helpers
The csf library provides flow control API (so called fx). You can get it from the path csf/fx
.
const each =
There are some of them:
spawn
Run sub-flow with the context inheritancefork
Spawn non-blocking sub-flowpayload
Wrap function to return it as payloadcancel
Cancel async operationreduce
Async reducemap
Async mapeach
Async each
etc. Read them all in the API reference.
Compatability
All you need is a node grater than 6.4.0 or regenerator (also babel tranform-regenerator) transpiler for older versions of node and compatibility with old browsers.
Special behavior of generators
Any returned es6 generator (or a generator result) will be executed until it returns final value.
const deferredCalc = { a + b;} { const a = 11; const b = 22; return ;} // 33
Keep in the mind, and there is an important part of the behavior - only last generator result will be returned.
And, if the generator contains no return operator, the value returned by last operator yield will be considered as the final result.
{ 1; 2; 3;} // 3
Executable values and payload
Functions, promises, and generators are considered to be executable values.
But all other types of values (as numbers, strings, booleans, objects, symbols, null and undefined) will be considered as, so-called, payload and will be returned to the parent function (or next generator step) as the result.
Creating standalone sequences
Csf allows you to wrap code with csf runner, thus obtaining a standalone asynchronous function.
;
Which will allow you to execute sequences without any additional access to the csf API. Also, such functions can take arguments.
// { type: 'entity', id: 33 }
Or use from an another sequence.
Csf;
Context in the ditails
Every sub-flow can access context of the initial flow. This means that you can manage the state or use API of your application from the child flow.
Even if your nested flow includes arrow-functions (which by nature can not access dynamic context), you can access context by returning classic functions.
Here is example how to get redux store state from nested arrow-function.
// Define helper, that can access state of the context { return this;} // Usual redux storeconst store = ; // Apply sequence with store as context // {sequence:'X'}
Channels
Foundation stone of CSP pattern is a channels. Channel can accumulate and radiate values. csf channel is not an object, but function, which returns promise with next value each time it called.
If channel is empty, then returned promise will be unresolved, until next value will be pushed to channel.
Sequence channels are not a in-flow API and can be used separately.
const createChannel } = ; const messages = ; messages;messages; ; // Hello; // World
Here is few examples of practicle usage of the channels.
Imagine situation when you should make request to the server each time user click on document. And you can not make more than 6 requests at a time.
Here is approximate solution on pure javascript:
const requestsQueue = ;let activeRequestsCount = 0; { if activeRequests >= 6 requestsQueue; else activeRequestsCount++; } document;
I'm not sure this example real works, but it shows how cumbersome it looks
And here how it can be solved with channels:
{ // Create two channels const clicks = ; const requests = ; // Add every click event to channel `clicks` document // Handle each next event through a while loop while clicks // Await superfluous requests while requests >= 6 requests; // Create http request and push response // to channel `responses` requests }
Cancellation
Most of fork processes in csf can be cancelled. To cancel any parallel process you should use special method Csf.cancel
.
const Csf = { const a = 1 2 3 4 5 const parallelFlow = Csffx; Csffx Csf;}// Val: 1// Val: 2// Val: 3
In this example we canceled process, created by async operator each
.
Author
Vladimir Kalmykov vladimirmorulus@gmail.com
License
MIT