All Projects → saperio → lightflow

saperio / lightflow

Licence: MIT license
A tiny Promise-inspired control flow library for browser and Node.js.

Programming Languages

javascript
184084 projects - #8 most used programming language

Projects that are alternatives of or similar to lightflow

Metasync
Asynchronous Programming Library for JavaScript & Node.js
Stars: ✭ 164 (+465.52%)
Mutual labels:  promise, chain, parallel, callback
do
Simplest way to manage asynchronicity
Stars: ✭ 33 (+13.79%)
Mutual labels:  promise, chain, parallel, callback
Bach
Compose your async functions with elegance.
Stars: ✭ 117 (+303.45%)
Mutual labels:  promise, parallel, callback
ProtoPromise
Robust and efficient library for management of asynchronous operations in C#/.Net.
Stars: ✭ 20 (-31.03%)
Mutual labels:  promise, parallel, callback
Rubico
[a]synchronous functional programming
Stars: ✭ 133 (+358.62%)
Mutual labels:  promise, parallel
Promise Parallel Throttle
It's kinda like Promise.all(), but throttled!
Stars: ✭ 72 (+148.28%)
Mutual labels:  promise, parallel
Poloniex Api Node
Poloniex API client for REST and WebSocket API
Stars: ✭ 138 (+375.86%)
Mutual labels:  promise, callback
Nedb Promises
A dead-simple promise wrapper for nedb.
Stars: ✭ 190 (+555.17%)
Mutual labels:  promise, callback
Ppipe
pipes values through functions, an alternative to using the proposed pipe operator ( |> ) for ES
Stars: ✭ 192 (+562.07%)
Mutual labels:  promise, chain
Amazon Mws
Amazon MWS NodeJS Wrapper
Stars: ✭ 196 (+575.86%)
Mutual labels:  promise, callback
YACLib
Yet Another Concurrency Library
Stars: ✭ 193 (+565.52%)
Mutual labels:  promise, parallel
Flowa
🔥Service level control flow for Node.js
Stars: ✭ 66 (+127.59%)
Mutual labels:  promise, parallel
Gollback
Go asynchronous simple function utilities, for managing execution of closures and callbacks
Stars: ✭ 55 (+89.66%)
Mutual labels:  promise, callback
Zcoil
Elegant access to data
Stars: ✭ 20 (-31.03%)
Mutual labels:  promise, callback
parley
Flow control harness for implementors. Builds a Deferred object that supports async/await, promise chaining, and conventional Node callbacks.
Stars: ✭ 23 (-20.69%)
Mutual labels:  promise, flow-control
woodpecker
woodpecker http client for Android
Stars: ✭ 17 (-41.38%)
Mutual labels:  promise, chain
P Map
Map over promises concurrently
Stars: ✭ 639 (+2103.45%)
Mutual labels:  promise, parallel
Asyncro
⛵️ Beautiful Array utilities for ESnext async/await ~
Stars: ✭ 487 (+1579.31%)
Mutual labels:  promise, parallel
Thunks
A small and magical composer for all JavaScript asynchronous.
Stars: ✭ 523 (+1703.45%)
Mutual labels:  promise, callback
Escape From Callback Mountain
Example Project & Guide for mastering Promises in Node/JavaScript. Feat. proposed 'Functional River' pattern
Stars: ✭ 249 (+758.62%)
Mutual labels:  promise, callback

Lightflow

A tiny Promise-inspired control flow library for browser and Node.js.

npm package

Introduction

Lightflow helps to run asynchronous code in synchronous way without the hassle.

Important note
Version 1 of Lightflow is not compatible with version 2+. Version 1 documentation, etc. has been moved to this branch.
For notable changes, see changelog.

Usage

  • Create an lightflow instance lightflow().
  • Describe your flow by adding a series of asynchronous functions - steps with .then, .race, .error, .catch and .done.
  • And then start, stop, restart, and even loop the flow as much as you needed, passing the new data on each run with .start, .stop and .loop.

Quick example

import lightflow from 'lightflow';

lightflow()
.then(({ next, error, data }) => {
	const { filename } = data;
	fs.readFile(filename, (err, content) => err ? error(err) : next({ raw : content, filename }));
})
.then(({ next, error, data }) => {
	try {
		data.parsed = JSON.parse(data.raw);
		next(data);
	}
	catch (err) {
		error(err);
	}
})
.done(data => {
	console.log(`This is content of ${data.filename}: ${data.parsed}`);
})
.catch(err => {
	console.log(`Error: ${err}`);
})
.start({ filename : 'file.json' })
;

Differences from Promise

  • Simpler API.
  • When you run asynchronous functions with callbacks, you should not care about promisification. Simply use them in your flow.
  • Lightflow is not for one-time execution thing. Once you described it, you can start, stop and restart it many times.

Installation

Browser

git clone https://github.com/saperio/lightflow.git

Use UMD module located in dist

<script src="lightflow.min.js"></script>

or just

<script src="https://unpkg.com/lightflow/dist/lightflow.min.js"></script>

Node.js

npm install lightflow --save

Node.js version >= 6.5

var lightflow = require('lightflow');

Node.js version >= 4.x

var lightflow = require('lightflow/lib/lts');

Node.js version >= 0.x

var lightflow = require('lightflow/lib/0.x');

API

All api function are divided in two groups: functions for describe the flow and functions for control the flow. Functions in the first group accept one or more tasks (with optional contexts). All of them return this for handy chaining.

lightflow

lightflow(params?: {
	datafencing?: boolean
})

Use lightflow() to create new flow instance. You can pass optional parameters object with some (just one for now) flags:

  • datafencing - (default - true) copy data object between steps and parallel tasks to prevent corrupting it in one task from another.

.then

.then(task: string | TaskFn | Lightflow, context?: any, ...): this
type taskFn = (param: taskFnParam) => void
type taskFnParam = {
	error: (err?: any) => void;
	next: (data?: any, label?: string) => void;
	count: (c: number) => void;
	data: any;
}

Overview

.then adds one or more tasks (with optional contexts) to the flow. If first parameter is a string, then other parameters are ignored and this step used as label. All the tasks run in parallel, their output data objects are merged and passed to the next step. Each task can be function or another Lightflow instance.

Task function parameters

Task function will receive single parameter with this fields:

  • next - function to be called, when task is finished. Can take data for the next step.
  • error - function to be called, when error occurred. You can pass error object to it.
  • count - function, can be used to indicate how many times task assume to call next before flow marks this task as complete. If not called - flow will accept only one next call and ignore results from the others from within current task.
  • data - data object from previous step.

Labels

With the labels, you can mark steps in the flow, which you can jump to from one step, ignore the others. Labels are added to the flow in this way: .then ('somelabel'), so we created a label named somelabel. To jump to this label, you need to call the function next inside the task with two parameters: data object, as usual, and the label name - next (data, 'somelabel');. If you pass the nonexistent label, then there will be no jump, the next step will be executed. Using labels you can jump only forward, this is done in order not to create an infinite loop. If you need to loop your flow, use .loop.

Examples

Simple, one step flow

lightflow()
.then(({ next, error, data }) => {
	doAsync(data, (err, out) => {
		if (err) {
			error(e);
		} else {
			next(out);
		}
	});
})
.start(somedata)
;

Here example with two parallel tasks on one step:

lightflow()
.then(
	({ next, data }) => {
		fetchUrl(data.url, remote => next({ remote }));
	},
	({ next, error, data }) => {
		fs.readFile(data.filename, (err, local) => err ? error(err) : next({ local }));
	}
)
.then(({ next, data }) => {
	const { remote, local } = data;
	// ... use remote and local
	next();
})
.start({
	url: 'google.com',
	filename: 'config.json'
})
;

In the following example task gets a list of filenames, reads files in parallel and passes results into a list of strings. If you comment count(data.length); line, all files will be read but only content of the first one will be passed to the next step.

lightflow()
.then(({ next, count, data }) => {
	let res = [];
	// data - array with filenames
	count(data.length);
	data.forEach(filename => {
		fs.readFile(filename, (err, content) => {
			res.push(content);
			next(res);
		});
	});
})
.then(({ next, data }) => {
	// here data - is array of files contents
	next();
})
.start(['file1.json', 'file2.json'])
;

Labels example:

lightflow()
.then(({ next, data }) => {
	next(data, 'jumphere')
})
.then(({ next, data }) => {
	// never get here
})
.then('jumphere')
.then(({ next, data }) => {
	// and here we are
	next(data);
})
.start()
;

Use one flow as task in another flow:

const parse = lightflow()
.then(({ next, error, data }) => {
	doParse(data.raw, (err, parsed) => err ? error(err) : next({ parsed }));
})
;

lightflow()
.then(({ next, data }) => {
	fetchUrl(data.url, raw => next({ raw }));
})
.then(parse)
.then(({ next, data }) => {
	const { parsed } = data;
	// use parsed
	next(data);
})
.start({ url: 'google.com' })
;

.race

.race(task: string | TaskFn | Lightflow, context?: any, ...): this
type taskFn = (param: taskFnParam) => void
type taskFnParam = {
	error: (err?: any) => void;
	next: (data?: any, label?: string) => void;
	count: (c: number) => void;
	data: any;
}

.race same as .then, except that the result only from the first completed task used for the next step.

lightflow()
.race(
	// first race task
	({ next, data }) => {
		setTimeout(() => {
			data.t1 = true;
			next(data);
		}, 50)
	},

	// second race task
	({ next, data }) => {
		setTimeout(() => {
			data.t2 = true;
			next(data);
		}, 100)
	}
)
// wait a little longer
.then(({ next, data }) => setTimeout(() => next(data), 100))
.then(({ next, data }) => {
	const { t1, t2 } = data;
	// here t1 === true and t2 === undefined
	next();
})
.start({})
;

.error

.error(handler: ErrorFn, context?: any): this
type ErrorFn = (param?: any) => any

Adds an error handler for the preceding step. Triggered when error occurs in the step it follows. Can be added many times. As a parameter gets the object passed to the error function. If handler returns something non-undefined, flow will continue and use this object as data for next step, otherwise flow will stop.

In the next example error is handled only from doAsync2, not from doAsync1.

lightflow()
.then(({ next, error }) => {
	doAsync1(err => err ? error(err) : next());
})
.then(({ next, error }) => {
	doAsync2(err => err ? error(err) : next());
})
.error(e => {
	console.log(`Error: ${e}`);
})
.start()
;

.catch

.catch(handler: CatchFn, context?: any): this
type CatchFn = (param?: any) => void

Adds an error handler to the flow. Catches errors from the all steps added before the catch. Can be added many times. As a parameter gets the object passed to the error function.

In the following example error is handled from both doAsync2 and doAsync1.

lightflow()
.then(({ next, error }) => {
	doAsync1(err => err ? error(err) : next());
})
.then(({ next, error }) => {
	doAsync2(err => err ? error(err) : next());
})
.catch(e => {
	console.log(`Error: ${e}`);
})
.start()
;

.done

.done(task: DoneFn, context?: any): this
type DoneFn = (data: any) => void

Adds a final task to the flow. Regardless of where it's defined, called after all other steps, if errors don't occur. Can be added many times. Task function gets data from last step.

lightflow()
.done(data => {
	console.log(data);
})
.then(({ next }) => {
	doAsync(out => next(out));
})
.start()
;

.start

.start(data?: any): this

Starts the flow. Takes optional data object, pass it to the first step.

.stop

.stop(handler?: StopFn, context?: any): this
type StopFn = (data?: any) => void

Stops the flow processing. Can take optional handler parameter (and it's context). This optional handler is called when current step is finished and output data is received from it.

In the following example the output from doAsync1 will be printed to the console.

const flow = lightflow()
.then(({ next }) => {
	doAsync1(out => next(out));
})
.then(({ next }) => {
	doAsync2(out => next(out));
})
.start()
;

flow.stop(data => {
	console.log(data);
})

.loop

.loop(flag?: boolean): this

Sets loop flag for the flow. If set, after call start flow do not stop after all steps processed and starts from first step, until stop called. Call loop(false) and flow will stop after last step.

This code prints increasing by one number every second:

lightflow()
.then(({ next, data }) => {
	setTimeout(() => next(++data), 1000);
})
.then(({ next, data }) => {
	console.log(data);
	next(data);
})
.loop()
.start(0)
;

Build and test

npm install
npm run build
npm run test

License

MIT

Note that the project description data, including the texts, logos, images, and/or trademarks, for each open source project belongs to its rightful owner. If you wish to add or remove any projects, please contact us at [email protected].