https://github.com/faressoft/flowa
🔥Service level control flow for Node.js
https://github.com/faressoft/flowa
api async control express expressjs flow javascript nodejs parallel promise restful router runner serial series sync task waterfall web
Last synced: 12 months ago
JSON representation
🔥Service level control flow for Node.js
- Host: GitHub
- URL: https://github.com/faressoft/flowa
- Owner: faressoft
- License: mit
- Created: 2018-06-19T02:10:01.000Z (over 7 years ago)
- Default Branch: master
- Last Pushed: 2018-09-10T18:23:53.000Z (over 7 years ago)
- Last Synced: 2025-03-19T00:16:59.159Z (12 months ago)
- Topics: api, async, control, express, expressjs, flow, javascript, nodejs, parallel, promise, restful, router, runner, serial, series, sync, task, waterfall, web
- Language: JavaScript
- Homepage:
- Size: 355 KB
- Stars: 74
- Watchers: 6
- Forks: 5
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
README

# Flowa
[](https://www.npmjs.com/package/flowa)
[](https://github.com/faressoft/flowa/blob/master/LICENSE)
[](https://gitter.im/flowa-control-flow/Lobby)
[](https://travis-ci.org/faressoft/flowa)
[](https://coveralls.io/github/faressoft/flowa?branch=master)
> Service level control flow for Node.js
One execution flow that runs mixed sync and async functions that uses either promises or callbacks running in parallel, sequentially or mixed. 🔥 **It can't be easier and more readable !**
# Hint
Check the [suggested way](#use-it-with-express) to use `Flowa` with `Express.js`.
# Demo

# Table of Contents
* [Features](#features)
* [Introduction](#introduction)
* [Installation](#installation)
* [Usage](#usage)
* [Shorthand Method](#shorthand-method)
* [Mixed Runners Types](#mixed-runners-types)
* [Promises](#promises)
* [Sync Tasks](#sync-tasks)
* [Terminating The Flow](#terminating-the-flow)
* [Jumping Between Tasks](#jumping-between-tasks)
* [Error Handling](#error-handling)
* [Factory Method](#factory-method)
* [ES6 Coding Style](#es6-coding-style)
* [Use It With Express](#use-it-with-express)
* [Best Practices](#best-practices)
* [Debugging Mode](#debugging-mode)
* [API](#api)
* [Flowa(flow[, name])](#constructor)
* [Flowa.create(flow[, name])](#create)
* [run(context, options)](#run)
* [License](#license)
## Features
* Writing more readable code for complex logic.
* Works with promises or callbacks.
* Works with sync or async tasks.
* Serial or parallel execution.
* No more callback hells.
* Jumping between tasks.
* Proper error handling.
* Timeouts.
## Introduction
Each `flow` is a set of `tasks`. It starts by a `compound task` which is basically a task that groups a set of `single` or other `compound` tasks. Single tasks are either async or sync functions that are executed and called by passing an object called `context` to allow sharing data between tasks and an optional `callback` function for async tasks that use callbacks instead of promises. Each compound task's sub tasks are executed by a `runner` that can be a `serial` execution (default type) or a `parallel` execution.
## Installation
```
npm install --save flowa
```
## Usage
We need to create a new Flowa object with our flow using `new Flowa(flow[, name])`, `Flowa.create(flow[, name])`, or just use the [Shorthand Method](#shorthand-method) it is much easier and recommended if you are not planning to execute the same flow again and again.
```js
var Flowa = require('flowa');
// Define the flow
var flowa = new Flowa({
// Runner type
type: 'serial',
// A task that uses a callback
asyncTaskWithCallback: asyncTaskWithCallback,
// A task that returns a promise
asyncTaskWithPromise: asyncTaskWithPromise,
// A sync task
syncTask: syncTask
});
```
Then we need to execute the flow.
```js
// To be used to share data between the tasks
var context = {};
// Execute the tasks
flowa.run(context).then(function(result) {
console.log(result);
}).catch(function(error) {
console.error(error);
});
```
And don't forget to write the code for your tasks.
```js
// A task that uses a callback
function asyncTaskWithCallback(context, callback) {
setTimeout(callback.bind(null, null, 'DummyValue1'), 500);
}
// A task that returns a promise
function asyncTaskWithPromise(context) {
return Promise.resolve('DummyValue2');
}
// A sync task
function syncTask(context) {
return 'DummyValue3';
}
```
Just put the 3 blocks of code together in one script and they will run smoothly.
### Shorthand Method
Is it possible to create a flow and execute it using a single function `.run()` that belongs to the Flowa class.
```js
Flowa.run({
// Runner type
type: 'serial',
// Do task1
task1: task1,
// Do task2
task2: task2
}).then(function(result) {
console.log(result);
}).catch(function(error) {
console.error(error);
});
```
### Mixed Runners Types
There are no limitations about mixing the runners types. Add `type` to the compound tasks to specify the runner type. But remember, it is not a good idea to make things too complex.
```js
var flowa = new Flowa({
// Runner type
type: 'serial',
// Do task1
task1: task1,
// Do task2 and task3 in parallel
group1: {
// Runner type
type: 'parallel',
// Do task2
task2: task2,
// Do task3
task3: task3,
// Do task4 and task5 in parallel
group2: {
// Runner type
type: 'serial',
// Do task4
task4: task4,
// Do task5
task5: task5
}
},
// Do task6
task6: task6
});
```
### Promises
You can return promises from your tasks instead of using callbacks. The callbacks will be called internally.
```js
function task1(context) {
return new Promise(function(resolve, reject) {
resolve();
});
}
```
### Sync Tasks
You can use sync tasks that doesn't return a promise and doesn't take a second callback argument.
```js
function task1(context) {
// Do something !!
}
```
### Terminating The Flow
You can terminate the flow (skip executing the remaining tasks) by calling the `done` method.
```js
function task1(context, callback) {
this.done();
callback();
}
```
### Jumping Between Tasks
You can jump forward and backward between tasks that belong to the same parent task and the runner type is `serial` by calling the `jump` method with the task name as first argument to jump into it after executing the current task completely. You can jump into a compound task too.
```js
function task1(context, callback) {
this.jump('task6');
callback();
}
```
### Loop and Retry
Since we have the ability to jump backward and forward, we can implement a task to try something and another task to check the result to decide either to jump back to the previous task or continue.
```js
function task1(context, callback) {
// We are just generating a random boolean value here
context.checkSomething = Math.random() >= 0.5;
callback();
}
/**
* Task
*
* @param {Object} context
* @param {Function} callback
*/
function task2(context, callback) {
if (context.checkSomething) {
return callback();
}
// Retry
this.jump('task1');
callback();
}
```
### Error Handling
The thrown errors and the errors passed as a first argument to the callback function can be handled by attaching a `.catch()` to the returend promise from `run()` method.
```js
// Using callbacks (Recommended)
function checkUser(context, callback) {
callback(new Error('User is not found'));
}
// Using the `throw` operator
function checkUser(context, callback) {
throw new Error('User is not found');
}
```
### Factory Method
Is it possible to create a new Flowa object by calling `.create()` method instead of using `new Flowa`.
```js
Flowa.create({
// Runner type
type: 'serial',
// Do task1
task1: task1,
// Do task2
task2: task2
}).run(context).then(function(result) {
console.log(result);
}).catch(function(error) {
console.error(error);
});
```
### ES6 Coding Style
You can use the shorthand syntax for naming the tasks by their functions names.
```js
var flowa = new Flowa({
// Runner type
type: 'serial',
// Shorthand format for task1: task1
task1,
// Shorthand format for task2:task2
task2,
// Shorthand format for task3:task3
task3,
// Shorthand format for task4:task4
task4,
// Shorthand format for task5:task5
task5,
// Shorthand format for task6:task6
task6
});
```
### Use It With Express
You can use `Flowa` to make more readable and maintainable `express.js` services.
#### App.js
To initilize your web server and load your services.
**Note**: No need to change the code, just add more services at the line 16.
```js
var express = require('express');
var Flowa = require('./index.js');
var app = express();
/**
* A mapping between services names and their handlers
* @type {Object}
*/
var handlers = {};
/**
* RESTful API services
* @type {Array}
*/
var services = [
{name: 'greeting.get', path: '/greeting/:name', method: 'get'}
];
/**
* Get a function to handle a specific service
*
* @param {String} name the name of the service
* @return {Function}
*/
function getServiceHandler(name) {
return function(req, res) {
var handler = handlers[name];
var context = {req: req, res: res};
handler.run(context).then(function() {
res.end();
}).catch(function(error) {
if (res.headersSent) {
return res.end();
}
res.status(500).send({
error: 'Something went wrong !'
});
console.error(error);
});
};
}
// Foreach service, define its route and attach a handler
services.forEach(function(route) {
handlers[route.name] = new Flowa(require('./' + route.name)),
app[route.method](route.path, getServiceHandler(route.name));
});
app.listen(3000, console.log.bind(null, 'listening ...'));
```
#### Greeting.get.js
An example of a service.
```js
/**
* Generate a greeting message
*
* @author Mohammad Fares
*/
var counter = 0;
/**
* Increment the greeting counter
*
* @param {Object} context
*/
function incrementGreetingCounter(context) {
context.counterValue = ++counter;
}
/**
* Generate a greeting message
*
* @param {Object} context
*/
function generateGreetingMessage(context) {
context.res.send({
message: 'Hello ' + context.req.params.name,
counter: context.counterValue
});
}
module.exports = {
// Runner type
type: 'serial',
// Increment the greeting counter
incrementGreetingCounter: incrementGreetingCounter,
// Generate a greeting message
generateGreetingMessage: generateGreetingMessage
};
```
## Best Practices
* Stick with one coding style.
* Define your flow object in a separated object or better in a separated module.
* Add comments for each task to get a quick overview about all the tasks at one place.
* Each single task should do literally only one task.
* Specifiy the runners types.
## Debugging Mode
To watch how the tasks being executed in realtime, you can activate the debug logging via the `debug` option.
```js
flowa.run(context, {debug: true});
```
## API
- Flowa(flow[, name])
To create Flowa objects
-
Flowa.create(flow[, name]) ⇒Flowa A factory method to create Flowa objects
-
Flowa.run(flow[, context, options]) ⇒Promise Create a flow and execute it
-
flowa.run(context[, options]) ⇒Promise Execute the flow
- task.done()
Skip the remaining tasks
- task.jump(taskName)
Jump into another task under the same parent after executing the current task
### Note
> A new instance from the class `Task` is created for each execution for each task.
## Flowa(flow[, name])
To create Flowa objects.
| Param | Type | Description |
|-------|---------------------|--------------------------------|
| flow | Object | A compound task |
| name | String | A name for the flow (Optional) |
## Flowa.create(flow[, name]) ⇒ Flowa
A factory method to create Flowa objects.
**Returns**: Flowa - a new Flowa object
| Param | Type | Description |
|-------|---------------------|--------------------------------|
| flow | Object | A compound task |
| name | String | A name for the flow (Optional) |
## Flowa.run(flow[, context, options]) ⇒ Promise
Create a flow and execute it.
**Returns**: Promise - resolve with the passed context object
| Param | Type | Description |
|---------|---------------------|------------------------------------------------------------|
| flow | Object | A compound task |
| context | Object | A shared object between the tasks (Optional) (default: {}) |
| options | Object | (Optional) |
## flowa.run(context, options) ⇒ Promise
Execute the flow. The Flowa object can be defined once and executed as many as you need.
**Returns**: Promise - resolve with the passed context object
| Param | Type | Description |
|---------|---------------------|------------------------------------------------------------|
| context | Object | A shared object between the tasks (Optional) (default: {}) |
| options | Object | (Optional) |
#### Options:
* **timeout**: a timeout for the flow in milliseconds. The promise will be rejected with an error object that has (code: `ETIMEDOUT`) if the timeout is exeeded (type: `Number`).
* **taskTimeout**: a timeout for the single tasks in milliseconds. The promise will be rejected with an error object that has (code: `ETIMEDOUT`) if the timeout is exeeded (type: `Number`).
* **autoInjectResults**: Inject the result of each task into the context automatically (type: `Boolean`) (default: `true`).
* **debug**: log the tasks' names in realtime (type: `Boolean`) (default: `false`).
* **debugCallback**: the debug logging function (type: `Boolean`) (default: `console.log`).
## task.done()
Skip the remaining tasks. Check [Terminating The Flow](#terminating-the-flow).
## task.jump(taskName)
Jump into another task under the same parent after executing the current task. Check [Jumping Between Tasks](#jumping-between-tasks).
| Param | Type | Description |
|----------|---------------------|--------------------------------|
| taskName | String | The name of the sibling task |
# License
This project is under the MIT license.