Chaining callbacks is nothing new. We've all seen the nested callbacks at some point. Also known as “callback hell”. This can quickly lead into some ugly and complicated code.
fn1(function() {
fn2(function() {
fn3()
})
})
Of course there are alternatives to avoiding nested callbacks as well, such as, promise chaining or using async/await. Instead, I want to call a single function that accepts a series of callbacks.
Real World Example
This isn't an ExpressJS tutorial, but we're going to approach our implementation in a similar manner. In ExpressJS, you can add a series of callback functions as middleware in your route methods before getting to your final handler function. Each function calls the next until the last.
router.get('/', middleware1, middleware2, handlerFn)
I'd say this looks better than nesting a bunch of callbacks.
Chaining Our Functions
We'll do something similar with our implementation and initiate it like so.
chain(fn1, fn2, fn3)
Let's start by declaring a few basic functions.
function fn1(cb) {
console.log('fn1')
if (cb) {
return cb()
}
}
function fn2(cb) {
console.log('fn2')
if (cb) {
return cb()
}
}
function fn3() {
console.log('fn3')
}
You'll notice the first two functions accept a callback argument and attempt to call the callback. The third function will be the final function being called, so we're not planning on passing or calling a callback within it. You could essentially chain as many functions as needed.
Below is our basic chain function.
function chain(...args) {
if (args.length > 0 && typeof args[0] === 'function') {
return args[0](() => chain.apply(null, args.slice(1)))
}
}
The function is defined to use rest parameters. This will allow us to pass as many arguments as we'd like into the function. We then check that the args array has more than zero elements and that the first element is a function type.
Within that statement we call the first callback, args[0]()
. In that first callback we pass in an argument of an anonymous arrow function returning back to the the chain function.
() => chain.apply(null, args.slice(1))
In this case, we set null for the first argument and for the second argument we pass in everything in the args array after the first element. This way we're not calling the first callback again.
Now when we run our code our functions should run like the following:

And our console logs should output in the following order:
> fn1
> fn2
> fn3
Try It Out
https://jsfiddle.net/t703msho/
Notes
We could also use the oddball arguments object within the chain function without a defined parameter, but you'd also need to convert it into an array.