Simplify the sequential business processes using step-flow

step-flow overview

Step-flow is a lightweight(without any libraries and less than 200 lines) business processes control library that allows to easily manage business logic by step using the syntax of middleware which is similar to express. It provides process control, step jumps, and unified error handling.

How was step-flow born?

In the process of writing a project, you would often encounter some business processes that are step by step, such as to implement a simple hosts agent. When the proxy server receives a request, you need to go through the following steps:

  • Get the requested hostname
  • Read configuration information of the hosts file
  • Resolve the final IP and construct new request information
  • send request
  • Handle response data
  • Sent to the client

In the above process, the previous processing results need to be used in the next step, and each step requires the use of some public data, that is, a context is required.

Of course, this is just a simple example, and there are so many similar processes.

At present, the more popular libraries that facilitate the management of processes are: step,async. These libraries have a variety of their own advantages and disadvantages, not here to comment.

If we are more familiar with the middleware of express, it is not difficult to find out that, in fact, middleware is a relatively good process-control mode. The handler of each process will receive three arguments (req, res, next). req and res run through the entire request - response cycle of the application, while next() controls the execution of the middleware. However, this is an ideal process control mode, so with reference to this model, step-flowwas born.

The features of step-flow

The use of step-flow is very similar to middleware of express:

  • Use use ([step,] fn1 [, fn2, ..., fnN]) to add process processing functions
  • The process processing functions are executed in the order they are added
  • In the process processing functions, next ([error, data]) is called to execute the next process processing function. If the error is passed, the subsequent function is no longer executed and the error handler is executed.
  • The process processing functions will receive a context object that runs through the entire process

The things that process processing function can do :

  • Execute any logic code
  • Operation context objects
  • Call the next process processing function
  • Synchronous or asynchronous

These are basically the same as the express middleware, in addition, step-flow are also added some new features:

  • In the process processing function, nextTo (step [, data]) can be called to jump to any of the existing steps
  • Call catch (fn [, fn1, ..., fnN]) to add error processing handler
  • Call the run ([context, step]) to execute the process processing function

The API of step-flow is simple, and the StepFlow instance has three methods: use()/catch()/run(), and methods that can be used within the process processing function: next()/nextTo().

Although it is simple, but it should have been able to most of the business process controls that are implemented in step by step. The following describes the specific use of the following methods.


Step-flow has been published to npm, and can be directly installed using npm:

npm install step-flow --save

yarn can also be used:

yarn add step-flow

Use examples

Suppose that we now have such a need:

  • Read a local .js file
  • Add the Source Map info to the end of the file
  • Obtain the MD5 value of the file content
  • Write the modified content to disk, add the MD5 value to the file name
  • If any error occurs, terminate the process and print the error messages

To achieve this, we need to create an instance of StepFlow and then add the processing functions for each step.

const StepFlow = require('step-flow');
const flow = new StepFlow();
.use('read-file', readFile)
.use('add-source-map', addSourceMap)
.use('get-md5', getMD5)
.use('write-file', writeFile)

Then, begin the execution of the process, call run([context, step]), the context context parameter can be passed when it is called, and each process processing function can modify the context object.{});

Next, begin to write the corresponding processing function for each step. These functions receive parameter (ctx, next, nextTo, data) when they are executed:

  • ctx <Any>: Context object, the first incoming parameter while calling the run ([context, step]), and it can manipulate the object to store and modify the data.
  • next <Function>: Call the next method.
  • nextTo <Function>: Jump to other steps.
  • data <Any>: Arbitrary data.

In general, we can put the processed data on the object of the context, and can also pass through the processed parameters to the next function through data.

Look at the processing function readFile read by file, this function is very simple, use fs.readFile() to read the contents of the file, then put the contents of the file into the context, and then call next().

function readFile (ctx, next) {
let {filePath} = ctx;
  fs.readFile(filePath, 'utf-8', function (err, data) {
if (err) {
} else {
ctx.fileContent = data;

Note: Here you can also directly use next () to pass the contents of the file to the next processing function, and the code is as follows.

function readFile (ctx, next) {
let {filePath} = ctx;
fs.readFile(filePath, 'utf-8', next);

After the first step is completed, then the contents of the file should be processed — add Source Map information, this is also relatively simple, and directly add a string behind the contents of the file.

function addSourceMap (ctx, next, nextTo, data) {
ctx.fileContent += '\n// # sourceMappingURL=/\n';

The remaining steps and patterns are the same, and there is no need to be longer enumerated. Full code:

The end

If you have any good advice, welcome to communicate with each other. Welcome star, too

Project address: