npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

lightflow

v2.0.0

Published

Tiny Promise-like control flow library for browser and Node.js

Downloads

8

Readme

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