flow-sync
v1.1.0
Published
A small JavaScript callbacks synchronizer for the browser and Node.js. A powerful alternative to using Promises, Flow.js offers you greater control, parallel execution, cleaner code and consistency across platforms.
Downloads
6
Maintainers
Readme
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 optionalfail
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
orfail
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
andfail
functions as parameters of the$.ajax()
function. As stated above, thefail
function is optional.Please note: Unless stated otherwise execution of the
fail
function will cause the flow to stop and trigger theonFail
function.flow
Accepts:
new Flow()
As an alternative to executing an
fn
function, we can give the task a sub-flow to execute instead. Ifflow
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 theonProgress
function, unlessnew 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 thefail
function.The
onProgress
function will receive anything returned bysuccess
.
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 theonFail
function. This function is optional. Any result returned by this function will be passed to theonFail
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
ortrue
ifdefaultSync
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. settingfailRetries: 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
orfunction
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
andfail
callback functionsThis method starts the execution of the flow.
Please note: The optional
complete
andfail
callbacks will execute in addition to theonComplete
andonError
functions we define in the options. The main difference is thatexecute(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 thestop()
method or by previously failing. This is because there is no reliable way to determine the exact stopping point in the tasks list. Useexecute()
only on flows that were completed and after pushing additional tasks, otherwise userestart()
.push()
Required: a task
object
orarray
of tasksThis 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
orarray
of tasksOptional:
complete
andfail
callback functionsThis 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
andfail
callback functionsUse 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.