flow-sync

1.1.0 • Public • Published

Flow.js

Flow.js is a small yet powerful callback synchronizer for browsers and Node.js. A powerful alternative to Promise based structure, Flow.js offers you greater control, cleaner syntax and consistency across various platforms, all in a tiny package with virtually no overhead.

new Flow(
[
    {fn: loadTemplate, success: parseTemplate},
    {fn: getUserData, success: processUserData},
    {fn: renderUI, sync: true},
    {fn: getUserAlbums, success: processUserAlbums},
    {fn: getAlbumImages, success: renderAlbumImages, sync: true}
]).execute();

Contents

Node.js installation and usage

To use Flow.js in Node.js projects, install its npm package.

npm install flow-sync

Usage:

var Flow = require('flow-sync');
 
new Flow([...]).execute();

Tasks

When creating a new Flow object you provide it with an array of tasks. A task can be a function with callbacks (an AJAX call for example) or another sub-flow. Flow is not a queue - tasks in the flow will execute in parallel by default.

Anatomy of a task

A task is an object with a number of attributes that define its behavior. Example:

{fn: getUserAlbums, success: processUserAlbums, fail: onError}

Task properties

Property Description Accepts Default
fn A function to be executed. function N/A
flow A sub-flow to be executed. If provided, fn will be ignored. new Flow() N/A
success A callback function to execute when the task is to be completed. function Empty function(){}
fail A callback function to execute when if the task fails for any reason. Under default conditions executing fail() will stop the flow. function Empty function(){}
sync If set as true will cause the task to await the completion of all previous tasks in the flow before executing. boolean false or true if defaultSync options is used
failRetries The number of times to repeat the task in case of failing. integer 0
retryInterval An interval (in milliseconds) between retry attempts. Used in conjunction with failRetries. integer or function 0
continueOnFail If set as true the failing of the task will not cause the entire flow to fail. boolean false
  • fn

    Accepts: function(success, fail)

    A function to be executed. The function receives a mandatory success and an optional fail functions.

    function myFunction(success)
    {
        setTimeout(function()
        {
            success('Completed', 1, true);
        }, 1000);
    }
     
    function myOtherFunction(success)
    {
        setTimeout(function()
        {
            success('Completed', {a: 1, b: 2});
        }, 2000);
    }
     
    new Flow(
    [
        {fn: myFunction},
        {fn: myOtherFunction},
    ]).execute();
     
    // Log: 
    // Completed 1 true
    // Completed {a: 1, b: 2}

    As we can see from the example above, after 1000 milliseconds we execute the success function with attributes we want it to receive.

    Please note: Execution of the success or fail function is mandatory, otherwise the task will not finish and the flow will never be completed.

    Let's see another example using jQuery to get AJAX data.

    function getUserAlbums(success, fail)
    {
        $.ajax(
        {
            url: '/get-user-albums'
            success: success,
            fail: fail
        });
    }
     
    new Flow(
    [
        {fn: getUserAlbums}
    ]).execute();

    In this example we assign our success and fail functions as parameters of the $.ajax() function. As stated above, the fail function is optional.

    Please note: Unless stated otherwise execution of the fail function will cause the flow to stop and trigger the onFail function.

  • flow

    Accepts: new Flow()

    As an alternative to executing an fn function, we can give the task a sub-flow to execute instead. If flow is defined, fn value will be ignored.

    function myFunction(success)
    {
        setTimeout(function()
        {
            success('Completed', 1, true);
        }, 1000);    
    }
     
    function myFunctionSuccess(param1, param2, param3)
    {
        console.log(param1, param2, param3);
    }
     
    function subFlowSuccess()
    {
        console.log('Sub-flow complete!');
    }
     
    var subFlow = new Flow(
    [
        {fn: myFunction, success: myFunctionSuccess}
    ]);
     
    new Flow(
    [
        {flow: subFlow, success: subFlowSuccess}
    ]).execute();
     
    // Log: 
    // Completed  1  true
    // Sub-flow complete!
  • success (optional)

    Accepts: function(attributes)

    A callback function that is passed as the first parameter of the fn function of the task. Executing it will successfully finish the task and trigger the onProgress function, unless new Error() is returned. This function is optional (if not defined, an empty function will be passed instead) and can receive any number of parameters.

    function myFunction(success)
    {
        setTimeout(function()
        {
            success('Completed', 1, true); // myFunctionSuccess() function is executed with parameters
        }, 1000);    
    }
     
    function myFunctionSuccess(param1, param2, param3)
    {
        console.log(param1, param2, param3);
    }
     
    new Flow(
    [
        {fn: myFunction, success: myFunctionSuccess}
    ]).execute();
     
    // Log: 
    // Completed  1  true
    • Returning new Error() by the function is equal to executing the fail function.
    • The onProgress function will receive anything returned by success.
  • fail (optional)

    Accepts: function(error)

    A callback function that is passed as the second parameter of the fn function of the task. Executing it will cause the task to fail and under default conditions fail the flow and trigger the onFail function. This function is optional. Any result returned by this function will be passed to the onFail function.

    function myFunction(success, fail)
    {
        setTimeout(function()
        {
            fail('Something went wrong!');
        }, 1000);    
    }
     
    function logError(error)
    {
        console.log(error)
    }
     
    new Flow(
    [
        {fn: myFunction, fail: logError}
    ]).execute();
     
    // Log: 
    // Something went wrong!
  • sync (optional)

    Accepts: boolean

    Default: false or true if defaultSync options is used.

    When the task is set as 'sync', its execution will be delayed until all the tasks before it had completed.

     
    function test1(success)
    {
        setTimeout(function()
        {
            success('Test1 complete');
        }, 2000);    
    }
     
    function test2(success)
    {
        setTimeout(function()
        {
            success('Test2 complete');
        }, 1000);    
    }
     
    function test3(success)
    {
        success('Test3 complete');  
    }
     
    function logResult(result)
    {
        console.log(result);
    }
     
    // No sync is used
    new Flow(
    [
        {fn: test1, success: logResult},
        {fn: test2, success: logResult},
        {fn: test3, success: logResult}
    ]).execute();
     
    // Log:
    // Test3 complete
    // Test2 complete
    // Test1 complete
     
     
    // The last task is defined as sync
    new Flow(
    [
        {fn: test1, success: logResult},
        {fn: test2, success: logResult},
        {fn: test3, success: logResult, sync: true}
    ]).execute();
     
    // Log:
    // Test2 complete
    // Test1 complete
    // Test3 complete
  • failRetries (optional)

    Accepts: integer

    Default: 0

    When we want to give the task a second chance to complete successfully after failing, we can use failRetries to define how many times we want it to retry the task. It is important to note that the number represents a number of times to retry the task after the task has failed the first time (i.e. setting failRetries: 2 will cause the task to execute additional two times).

    var num = 0;
     
    function myFunction(success, fail)
    {
        num++;
        
        if (num % 3 > 0)
        {
            fail(num + ' is not divisible by 3');
        }
        else
        {
            success('Great success! ' + num + ' is divisible by 3');
        }
    }
     
    function logSuccess(result)
    {
        console.log(result);
    }
     
    function logError(error)
    {
        console.log(error);
    }
     
     
    new Flow(
    [
        // Give the task another 2 attempts to succeed
        {fn: myFunction, success: logSuccess, fail: logErrors, failRetries: 2} 
    ]).execute();
     
    // Log: 
    // 1 is not divisible by 3
    // 2 is not divisible by 3
    // Great success! 3 is divisible by 3
  • retryInterval (optional)

    Accepts: integer or function

    Default: 0

    Used in conjunction with failRetries, we can delay the execution of another retry by a defined amount of milliseconds. We can instead pass a function to dynamically modify the delay time (this function must return a number).

    var num = 0;
     
    function myFunction(success, fail)
    {
        num++;
        
        if (num % 3 > 0)
        {
            fail(num + ' is not divisible by 3');
        }
        else
        {
            success('Great success! ' + num + ' is divisible by 3');
        }
    }
     
    function setRetryInterval()
    {
        return num * 1000;
    }
     
    function logSuccess(result)
    {
        console.log(result);
    }
     
    function logError(error)
    {
        console.log(error);
    }
     
     
    new Flow(
    [
        // Dynamically set the retry interval
        {fn: myFunction, success: logSuccess, fail: logErrors, failRetries: 2, retryInterval: setRetryInterval} 
    ]).execute();
     
    // Log: 
    // 1 is not divisible by 3
    //
    // (After 1000ms)
    // 2 is not divisible by 3
    //
    // (After 2000ms)
    // Great success! 3 is divisible by 3
  • continueOnFail (optional)

    Accepts: boolean

    Default: false

    Setting this as true will not cause a failed task to fail the entire flow. If 'failRetries' is used, the task will still attempt to perform retries.

    function test1(success)
    {
        setTimeout(function()
        {
            success('Test1 complete');
        }, 2000);    
    }
     
    function test2(success, fail)
    {
        setTimeout(function()
        {
            fail('Test2 had failed!');
        }, 1000);  
    }
     
    function test3(success)
    {
        success('Test3 complete') ; 
    }
     
    function logResult(result)
    {
        console.log(result);
    }
     
    function logError(error)
    {
        console.log(error);
    }
     
    // This flow will not be completed, failing on test2
    new Flow(
    [
        {fn: test1, success: logResult},
        {fn: test2, success: logResult, fail: logError},
        {fn: test3, success: logResult, sync: true}
    ]).execute();
     
    // Log:
    // Test2 had failed!
     
     
    // This flow will be completed even though test2 is set to fail
    new Flow(
    [
        {fn: test1, success: logResult},
        {fn: test2, success: logResult, fail: logError, continueOnFail: true},
        {fn: test3, success: logResult, sync: true}
    ]).execute();
     
    // Log:
    // Test2 had failed!
    // Test1 complete
    // Test3 complete

Options

Options is the second parameter you can provide to the Flow constructor to define general callbacks and certain behaviors of your flow.

new Flow([...], 
{
    onComplete: function()
    {
        console.log('Flow has completed successfully'),
    }, 
    onFail: function(error)
    {
        console.log('Flow has failed:', error);
    }, 
    onProgress: function(result)
    {
        console.log('Task returned result:', result);
    }
}).execute();
Option Description Accepts Default
onComplete A callback function to be executed after each successful execution of the flow. function N/A
onFail A callback function to be executed each time the entire flow fails. If the fail function of the failed task returns a value, this value will be passed to the onFail function function N/A
onProgress A callback function that will execute each time a task completes successfully. If the success function of the task returns any value except new Error(), this value will be passed to the onProgress function. function N/A
defaultSync If set as true, defaultSync will cause all tasks to become sync by default. boolean false

Methods

Methods are functions used to execute, stop or add tasks to the flow. Methods can be chained.

Method Description Parameters
execute() Starts the execution of the flow. Accepts two optional functions for complete and fail callbacks.
push(tasks) Pushes tasks to the end of the task list. Can accept a single task object or an array of tasks.
pushAndExecute(tasks) A shorthand method, equals flow.push(tasks).execute(). Accepts tasks as a first parameter and optional callbacks as second and third.
stop() Stops the execution of the flow and prevents the execution of callbacks of currently executing tasks. N/A
restart() Restarts the execution of the flow. Accepts two optional functions for complete and fail callbacks.
  • execute()

    Optional: complete and fail callback functions

    This method starts the execution of the flow.

    Please note: The optional complete and fail callbacks will execute in addition to the onComplete and onError functions we define in the options. The main difference is that execute(complete, fail) callbacks will execute only once, while the ones we set in the options will execute every time the flow completes or fails.

    function onExecuteComplete()
    {
        console.log('Flow execution was completed');
    }
     
    var flow = new Flow([...], 
    {
        onComplete: function()
        {
            console.log('Done and done'),
        }
    });
     
    flow.execute(onExecuteComplete);
     
    // Log:
    // Done and done
    // Flow execution was completed

    Please note: It is not advised to use execute() on a flow that was stopped by the stop() method or by previously failing. This is because there is no reliable way to determine the exact stopping point in the tasks list. Use execute() only on flows that were completed and after pushing additional tasks, otherwise use restart().

  • push()

    Required: a task object or array of tasks

    This method is used to add additional tasks to the end of the tasks list of the flow.

    var flow = new Flow([...]);
     
    flow.push({fn: test1}).push(
    [
        {fn: test2},
        {fn: test3},
        {fn: test4}
    ]).execute();
  • pushAndExecute()

    Required: a task object or array of tasks

    Optional: complete and fail callback functions

    This is a shorthand method, equals flow.push(...).execute().

  • stop()

    This method will stop the execution of the flow and prevent any currently executing tasks from executing their callbacks.

  • restart()

    Optional: complete and fail callback functions

    Use this method to restart the flow from the first task.

Examples

  • Simple images pre-loading

     
    var loadedImages = [];
     
    function loadImage(imageUrl, success, fail)
    {
        var image = new Image();
     
        image.onload = success;
        image.onerror = fail;
        image.src = imageUrl;
    }
     
    function addLoadedImageToArray(event)
    {
        loadedImages.push(event.target); // Push the loaded image to the loadedImages array for later use
    }
     
    function imageLoader(imageUrls)
    {
        var tasks = [];
     
        if (imageUrls && imageUrls.length)
        {
            // Create our tasks list by looping over the imageUrls array
            for (var i= 0, l=imageUrls.length; i<l; i++)
            {
                tasks.push(
                {
                    fn: loadImage.bind(null, imageUrls[i]), // We'll use function.bind to bind imageUrl attribute to the fn
                    success: addLoadedImageToArray,
                    continueOnFail: true // We do not want to fail the entire loading cycle if one of our images fails to load
                });
            }
     
            new Flow(tasks,
            {
                onComplete: function(a)
                {
                    // For the sake of this example, we'll add our loaded images to the BODY element
                    for (var i=0, l=loadedImages.length; i<l; i++)
                    {
                        document.body.appendChild(loadedImages[i]);
                    }
                }
            }).execute();
        }
    }
     
    // Lets load some images
    imageLoader(['images/1.jpg', 'images/2.jpg', 'images/3.jpg', 'images/4.jpg', 'images/5.jpg']);

Contributors

Special tanks to Shane Goodson for helping me write this README and testing.

License

This project is licensed under the MIT License - see the LICENSE.txt file for details.

Package Sidebar

Install

npm i flow-sync

Weekly Downloads

1

Version

1.1.0

License

MIT

Last publish

Collaborators

  • sgranin