In JavaScript, managing the order of function execution can be tricky, especially when dealing with asynchronous operations like setTimeout
. Recently, I encountered an interesting problem that required executing a series of functions in a specific order. Let's explore the problem and a simple solution.
The Problem
Suppose we have two functions, f1
and f2
, defined as follows:
const f1 = () => {
setTimeout(() => {
console.log('1');
}, 1000);
}
const f2 = () => {
setTimeout(() => {
console.log('2');
}, 500);
}
If we run f1()
followed by f2()
, the output is not what we expect:
f1();
f2();
/*
Output:
2
1
*/
However, we want to execute f1
first and then f2
. So, the expected output is:
/*
After 1 second, print 1
Then after 0.5 seconds, print 2
*/
The Initial Solution
To control the order of execution, one approach is to pass f2
as a callback to f1
:
const f1 = (cb) => {
setTimeout(() => {
console.log('1');
cb();
}, 1000);
}
const f2 = () => {
setTimeout(() => {
console.log('2');
}, 500);
}
Now, calling f1(f2)
ensures that f2
runs after f1
:
f1(f2);
/*
Output:
1
2
*/
Adding a Third Function
Now, let's introduce a third function, f3
, that needs to be executed after f2
. To maintain clean and logical code, we continue using callbacks:
const f1 = (cb) => {
setTimeout(() => {
console.log('1');
cb();
}, 1000);
}
const f2 = (cb) => {
setTimeout(() => {
console.log('2');
cb();
}, 500);
}
const f3 = () => {
console.log('done');
}
const f2Callback = () => {
f3();
}
const f1Callback = () => {
f2(f2Callback);
}
f1(f1Callback);
This approach works, but it can become unwieldy when dealing with a large number of functions. What if we want a more generic solution that can handle any number of functions in a specific order?
The Magic Function
Let's create a magic function, magic
, that takes an arbitrary number of functions and executes them in the order they are received:
const magic = (...args) => {
// Start off with assigning callback f as the last function
let f = args[args.length - 1];
const iterate = (i, f) => {
return () => args[i](f);
}
for (let i = args.length - 2; i >= 0; i -= 1) {
f = iterate(i, f);
}
f();
}
magic(f1, f2, f3);
With the magic
function, we can specify the order of execution as f1
-> f2
-> f3
:
/*
Output:
1
2
done
*/
This approach allows us to execute functions in a defined order, making our code more modular and easier to maintain.
Other Options and Considerations
Using Promises: Another option would be to return a Promise from both the functions. By chaining Promises, you can control the order of execution and handle asynchronous operations more elegantly.
API Design Perspective: From an API design perspective, if you consider these
f*
functions as a global library, it is a logical choice to allow the addition of callback functions. This design allows users of the library to execute custom logic after the library functions complete execution, enhancing the flexibility of your library.
Check out the code on JSFiddle
Happy coding!