Running a list of function
Published on: 21st Sep 2024
Overview
In any system, a main process requires to run a series of sub-process to process the data. For example, in Node Express web framework, it allows the developer to have middleware to pre-process the request. The developer will have to decide whether it should continue with next process/step. This is useful and convenient to split a huge process into multiple smaller sub-processes.
Let's try to develop a class that keeps multiple sub-processes (i.e., function) for a process and run all functions against the data.
Sample class
The code explains everything.
class funcList {
_fn_exec_list = [];
/**
* Add function to tbe execution list.
* @param {Function|Function[]} fn
*/
add(fn) {
if (fn) {
if (typeof fn == 'function') {
// if it is a Function type, append it to the _fn_list.
this._fn_exec_list.push(fn);
}
else if (Array.isArray(fn)) {
// if it is an Array, add each function to the _fn_list.
fn.forEach(a2 => {
this._fn_exec_list.push(a2);
});
}
}
}
/**
* Run all functions.
* @param {Object} [user_data] - the user data to be processed/referenced by each function.
* @param {Function} [oncompletion] - the last function to be executed.
*/
run(user_data, oncompletion) {
// capture the number of functions to be executed.
const mx = this._fn_exec_list.length;
let idx = -1;
// we need a context to keep the user's data
const context = {
step: idx,
user_data: user_data,
};
if (mx > 0) {
// run the functions one after another.
const next_fn = () => {
// move to next function in the array.
idx++;
// the step value will be maintained here and it
// cannot be changed outside of this function.
context.step = idx;
let fn;
if (idx < mx) {
fn = this._fn_exec_list[idx];
}
else {
// if there is no more function to be executed,
// run the oncompletion callback and exit.
if (oncompletion) {
oncompletion();
}
return null;
}
// execute the function (fn) and passes the 'next_fn' function as parameter.
// As a result, the 'fn' will have to decide whether to continue next
// step or stop the process.
fn(context, next_fn);
};
// execute the first function
next_fn();
}
else if (oncompletion) {
oncompletion();
}
}
}
//------------------------------------------------------------------------------
// create a new instance.
const fn_list = new funcList();
// add 1 function to show the opening balance.
fn_list.add((context, next) => {
console.log(`step:${context.step}, opening balance: ${context.user_data.balance}`);
next();
})
// add 3 functions to process the balance by adding interest.
fn_list.add([
(context, next) => {
context.user_data.balance += 3;
console.log(`step:${context.step}, balance: ${context.user_data.balance}`);
next();
},
(context, next) => {
context.user_data.balance += 5;
console.log(`step:${context.step}, balance: ${context.user_data.balance}`);
next();
},
(context, next) => {
context.user_data.balance += 10;
console.log(`step:${context.step}, balance: ${context.user_data.balance}`);
next();
},
]);
// here's an account to be processed by the above process.
const account = {
account_number: '1234',
balance: 1000,
};
fn_list.run(account,
() => {
console.log('final step');
});
/*
//output:
step:0, opening balance: 1000
step:1, balance: 1003
step:2, balance: 1008
step:3, balance: 1018
final step
*/
Characteristics of this technique
-
All sub-processes will be called synchronously.
-
If here is an async function, you may call the
next()
function inthen()
. -
You can stop the process in any step by not calling
next()
function that was coming in through the function parameter. -
You can find out which sub-process was running.
For example, you want to validate a person's data.
const fn_list2 = new funcList(); fn_list2.add(function check_name(context, next) { //console.log('checking the name'); next(); }); fn_list2.add(function check_age(context, next) { //console.log('checking age'); next(); }); console.log('dump the sub processes:'); fn_list2._fn_exec_list.forEach((f2) => { console.log('sub-process: ' + f2.name); });
And here is the output:
dump the sub processes: sub-process: check_name sub-process: check_age
-
You may conditionally add the sub-process. For example, you are developing a system for a factory. The checking process is different between the semi-finished product and finished product. As a result, you program might look like this:
function check_semi_finish_product(context, next) {..} function check_finish_product(context, next) {..} const fn_list2 = new funcList(); if (is_finish_product(data)) { fn_list2.add(check_finish_product); } else { fn_list2.add(check_semi_finish_product); } fn_list2.run(data);
Breaking the entire long checking process into
check_semi_finish_product()
andcheck_finish_product()
, it will ease you from testing the accuracy of the sub-processes. Who knows one day in the future where you might have to add a new process to check the raw material. If this happened, you will have to declare two new functions:is_raw_material()
andcheck_raw_material()
. And then, modify theif
decision and plug thecheck_raw_material()
sub-process whenis_raw_material()
returns true. This reduces the risk of crashing the app on the changes. This also reduces the time needed to test the changes.
Use case
- Processing HTTP request - checking the request, adding new header, etc.
- Data validation process - adding new rule and removing old rule with ease.
- Data processing - process the data based on different runtime value and configuration.
- Upon initializing data entry form - loading the drop down list item and setup the form.
- Upon cleaning up a data entry form before closing - removing the data cache in the localStorage, notifying the server that the form is closing or calling any cleanup function that was added during the user interaction.
- Run data exchange one after another - for example, you want to sends the new changes to another server one after another to avoid flooding the receiving server. This is similar like running a single threaded queue.
Conclusion
This class provides a convenient way to swap the sub-process sequence, removal or adding of sub-process.
Related posts
Jump to #JAVASCRIPT blog
Jump to #NODEJS blog
Author
Lau Hon Wan, software developer.