Package Exports
- move-on
This package does not declare an exports field, so the exports above have been automatically detected and optimized by JSPM instead. If any package subpath is missing, it is recommended to post an issue to the original package (move-on) to support the "exports" field. If that is not possible, create a JSPM override to customize the exports field for this package.
Readme
Description
move-on
is a very light module that creates a chain of synchronous or asynchronous functions and sets its own inner userContext object for this
keyword for all chained functions.
- Any bugs found? Give me to know on dev.rafalko@gmail.com or on GitHub
Installation
npm install move-on
const moveOn = require('move-on');
const functionList = [funA, funB];
const userContext = {};
moveOn(functionList, userContext, onDone, onError);
function funA(resolve,reject){
resolve();
}
function funB(resolve,reject){
resolve();
}
function onDone(userContext,reject){
console.log('all functions succeeded!');
//continuation code here
}
function onError(userContext,msg){
console.error(msg);
}
Usage
moveOn(list, userContext, done, catch)
list
[Array]
- The [Array] list should contain the [Function] items that will be chained and executed back-to-back.
- Avoid using (or use wisely)
arrow functions
. They cannot be binded, so theuserContext
will not be set for them.
userContext
[Object|null]
- All [Function] items are automatically binded to the
userContext
Object. - Each [Function] item has the access to the
userContext
Object bythis
keyword. - The [Object]
userContext
properties can be set and get in any queued function, thereby shared between each function of the queue block. - Pass an empty object
{}
to create the newuserContect
for the chain. - In order not to bind the [Function] items (to keep the current
this
reference), setuserContext
tonull
done(userContext,reject)
[Function]
- The [Function]
done
is automatically attached at the end of the chain and called as the last one (if all previous functions have been resolved). [sample] - Arguments passed through
done
:- [0] [Object] userContext for
this
; It is useful if you definedone
function as anarrow function
(arrow functions
are not intended to be binded by default, so the access to the userContextthis
object can be gained only via the argument). - [1] [Function]
reject
function; You can still reject the chain in thedone
function by callingreject()
.
- [0] [Object] userContext for
catch(userContext)
[Function]
- The [Function]
catch
is automatically attached at the end of the chain ascatch()
function and executed in case of the rejection of any queued function (when thereject()
is called). - Arguments passed through
catch
:- [0] [Object] userContext for
this
; It is useful if you definecatch
function as anarrow function
(arrow functions
are not intended to be binded by default, so the access to the userContextthis
object can be gained only via the argument).
- [0] [Object] userContext for
Chained functions
- The [Function]
resolve
and [Function]reject
arguments are passed through each chained [Function] item. - When
resolve
is called, the further chained [Function] item is called. - When
reject
is called, thecatch
callback function is called. [see below] - Both
resolve
andreject
can be called with any number of arguments. They will be passed to the further function in the chain, when resolved, or to thecatch
function, when rejected. [see below]
function fetchData(resolve,reject){
if(foo) {
resolve(foo);
} else {
reject('foo is not defined.');
}
}
Behaviour
- Mind that both
resolve
andreject
do not end function execution. In order to end function execution, usereturn resolve()
orreturn reject()
. [sample] - The
resolve
andreject
can be called many times in the same function:- when you call
resolve
callback function twice, it runs the further part of the chain twice. You can, for example, call theresolve
function once, then redefine the properties in the userContextthis
object, then callresolve
again. - you can also call
resolve
in thefor
loop. [sample] - you can call
reject
twice for example to pass two different error messages to thecatch
function. [sample]
- when you call
- You can also put one function chain into another and thereby create forks of chains [sample]
Samples
The sample of sync and async functions used in the chain.
const moveOn = require('move-on');
//set the list of functions in order
const functionList = [setInitialData,getName,getAge];
//set the userContext accessible via this keyword
const userContext = {};
moveOn(functionList, userContext, onDone, onCatch);
function onDone(userContext,reject){
console.log(`The new employee of ${this.company} company, based in ${this.city}, is ${this.age}yo ${this.name}.`);
//=> The new employee of PierogSoft company, based in Warsaw, is 26yo John Doe.
}
function onCatch(userContext,msg){
console.log('Something went wrong: ',msg);
}
function setInitialData(resolve,reject){
//this is a sample of synchronous function in the chain
//this data will be accessible via this keyword in all chained functions
this.company = 'PierogSoft';
this.city = 'Warsaw';
resolve();
}
function getName(resolve,reject){
//this is a sample of asynchronous function in the chain
//it runs resolve() when it's ready
setTimeout(()=>{
//mind that arrow function takes userContext this from the parent function
this.name = 'John Doe';
resolve();
},1000);
}
function getAge(resolve,reject){
//this is another sample of asynchronous function in the chain
//it runs resolve() when it's ready
setTimeout(()=>{
//mind that arrow function takes userContext this from the parent function
this.age = 26;
resolve();
},600);
}
The userContext
object is defined with some initial properties.
The arguments can be passed through resolve
and reject
functions.
const moveOn = require('move-on');
//set userContext object with some initial properties
const userContext = {
name:'John Doe',
age:25
};
moveOn([check], userContext, onDone, onCatch);
function check(resolve,reject){
//check if initial properties are accessible
console.log(this.name); //John Doe
console.log(this.age); //25
if(this.name&&this.age){
//pass the argument to the further function
resolve('person');
} else {
//pass the argument to the catch function
reject('The person was not defined.');
}
}
function onDone(userContext,reject,isPerson){
//get the 'isPerson' argument pass in the previous function
if(isPerson==='person') console.log(`New person: ${this.name} (${this.age}yo).`);
}
function onCatch(userContext,msg){
console.log('Something went wrong: ',msg);
}
Mind that the rejected function terminates the execution of the further part of the chain and executes catch
function immediately.
const moveOn = require('./index.js');
moveOn([a,b,c], {}, onDone, onCatch);
function a(resolve,reject){
resolve();
}
function b(resolve,reject){
reject('oops!');
}
function c(resolve,reject){
//it's been never called!
resolve();
}
function onDone(userContext,reject){
//it's been never called!
}
function onCatch(userContext,msg){
console.log(msg); //oops!
}
Mind how return resolve()
ends the function.
See how the accessibility of properties defined after resolve()
works in the further functions.
const moveOn = require('move-on');
moveOn([a,b,c], {}, onDone, onCatch);
function a(resolve){
resolve();
console.log('this is still logged!');
//lets define some properties after resolve() call
this.name = 'John Doe';
this.age = 25;
}
function b(resolve){
//properties are inaccessible because they were defined after this function was executed
console.log(this.name); //undefined
console.log(this.age); //undefined
return resolve();
console.log('this is nog logged');
}
function c(resolve){
setTimeout(()=>{
//properties are accessible because c function works asynchronously
console.log(this.name); //John Doe
console.log(this.age); //25
resolve();
},500);
}
function onDone(userContext,reject){
//properties are accessible because onDone function is fired after async c function is resolved
console.log(this.name); //John Doe
console.log(this.age); //25
}
function onCatch(userContext){
//it's been never called
}
Chain can be forked!
Mind that the inner chain gets its parent this
as the userContext
object.
const moveOn = require('move-on');
moveOn([a,b,c], {}, onDone, onCatch);
/* Order of executions:
* a
* x
* y
* z
* b
* c
*/
function a(resolve){
resolve();
}
function b(resolve){
//lets define some properties
this.name = 'John Doe';
this.age = 25;
//set this as the userContext of the inner queue
moveOn([x,y,z], this, function(){
resolve(); //resolve b after x, y and z are succeeded!
}, onCatch); //one onCatch function for all rejected functions
}
function c(resolve){
resolve();
}
function x(resolve){
//check if the parent userContext is accessible
console.log(this.name); //John Doe
resolve();
}
function y(resolve){
resolve();
}
function z(resolve){
resolve();
}
function onDone(userContext,reject){
console.log('Success!');
}
function onCatch(userContext){
//it's been never called
}
Notice how the instructions placed before and after resolve()
behave.
You can move on to the last function, then set some final data, then get back to the initial function.
const moveOn = require('move-on');
moveOn([a,b,c], {}, onDone, onCatch);
/* Order of executions:
a before
x before
y before
z before
b before
c before
DONE!
c after
b after
z after
y after
x after
a after
*/
function a(resolve){
console.log('a before');
resolve();
console.log('a after');
}
function b(resolve){
moveOn([x,y,z], this, function(){
console.log('b before');
resolve();
console.log('b after');
}, onCatch);
}
function c(resolve){
console.log('c before');
resolve();
console.log('c after');
}
function x(resolve){
console.log('x before');
resolve();
console.log('x after');
}
function y(resolve){
console.log('y before');
resolve();
console.log('y after');
}
function z(resolve){
console.log('z before');
resolve();
console.log('z after');
}
function onDone(userContext,reject){
console.log('DONE!');
}
function onCatch(userContext){
//it's been never called
}
A part of the chain is ran in the loop.
const moveOn = require('./index.js');
const productsData = {
id: ['ab12','xy34'],
products:[]
};
moveOn([init,findPrice,isAvailable], productsData, onDone, onCatch);
function init(resolve){
this.iter = 0; //set iter to find out when all async functions are done
for(let i in this.id){
//run findPrice() and isAvailable() back-to-back for each ID
resolve(this.id[i]);
}
}
function findPrice(resolve,reject,getID){
setTimeout(()=>{
//assume that we asynchronously fetch the price due to the ID of the product
var price = Math.round(Math.random()*100)+'$';
resolve(getID,price);
},500);
}
function isAvailable(resolve,reject,getID,getPrice){
setTimeout(()=>{
//assume that we check if the product of specified ID is available
var available = Math.random()>.5;
//save the data about the product
this.products.push({id:getID,price:getPrice,available:available});
this.iter++;
//if this is the last async function, resolve() and finish the chain
if(this.iter===this.id.length) resolve();
},500);
}
function onDone(userContext,reject){
console.log('Success!');
console.log(this.products);
//{ id: 'ab12', price: '71$', available: true },
//{ id: 'xy34', price: '31$', available: false }
}
function onCatch(userContext){
//it's been never called
}
See how the multiple reject()
execution can be used for a validation.
const moveOn = require('move-on');
const validFunctions = [
isString,
hasAtSign,
hasMultipleAtSigns,
hasSpecialChars,
hasSpaces,
hasDoubleDots,
isAvailable
];
const address = {
email: 'John..doe@gm@ail.com'
};
moveOn(validFunctions, address, onDone, onCatch);
/* console =>
* Invalid: The value can contain only one @ sign.
* Invalid: The value cannot contain capital letters.
* Invalid: The value cannot contain double dots.
* Invalid: The John..doe@gm@ail.com address is unavailable.
*/
function isString(resolve,reject){
if(typeof this.email !=='string') return reject('The value must be a String.');
resolve();
}
function hasAtSign(resolve,reject){
if(!(/@/).test(this.email)) reject('The value must contain @ sign.');
resolve();
}
function hasMultipleAtSigns(resolve,reject){
if((/@.*@/).test(this.email)) reject('The value can contain only one @ sign.');
resolve();
}
function hasSpecialChars(resolve,reject){
if(!(/^[a-z.\-@_]+$/).test(this.email)) reject('The value cannot contain capital letters.');
resolve();
}
function hasCapitals(resolve,reject){
if((/[A-Z]/).test(this.email)) reject('The value cannot contain special characters.');
resolve();
}
function hasSpaces(resolve,reject){
if((/\s/).test(this.email)) reject('The value cannot contain spaces.');
resolve();
}
function hasDoubleDots(resolve,reject){
if((/\.{2,}/).test(this.email)) reject('The value cannot contain double dots.');
resolve();
}
function isAvailable(resolve,reject){
//this.atLeastOneErr is set in the onCatch() function
//if the address is invalid, it makes no sense to check if is available
if(this.atLeastOneErr) return;
setTimeout(()=>{
var available = Math.random()>.5;
if(available) resolve(`Success! The ${this.email} address is available!`);
if(!available) reject(`The ${this.email} address is unavailable.`);
},234);
}
function onDone(userContext,reject,msg){
console.log(msg);
}
function onCatch(userContext,msg){
this.atLeastOneErr = true;
console.log('Invalid: ' + msg);
}