Callbacks are a programming pattern in which we pass a function pointer to another function to ensure an order of operations in asynchronous code.
Example
We have a function defined as follows:
int getId(){
// takes a long time and eventually returns an id...
}And we want to do something with the id once it has returned to us. How do we ensure that as soon as getId is done we make it for example buy something, without blocking our CPU by polling? One such way of doing so is using callbacks.
Because function pointers in C are just addresses in memory, we can pass them to other procedures so that they can run them as if they were defined within their scope.
Taking our previous example, we can do this:
void buyThing(int id){
//do something with the id
}
void getIdAndModify(void (*operation) (int id)){
int id = //get an id...
operation(id);
}
int main(){
getId(buyThing);
//execution continues...
doOtherThing();
}In the code above, getIdAndModify will return immediately when calling it in main, and it will continue executing, for example with doOtherThing. However, because we passed a function pointer to it, when it completes, it will call buyThing() with the id that we got. This is the idea behind callbacks.
Callbacks require some kind of async implementation to work as intended, which can be threads or interrupts for example.
Callback Hell
Callback hell is the situation where you need pass a callback to a callback to a callback and so on (if you need to do one thing, then the next, then the next). This results in deeply nested code that can be difficult to read, refactor, or maintain. Is most common in back-end languages that use callbacks as part of their design philosophy, like Node.js.
Non-testable
Node.js is indeed a big offender of this, but a lot of modern codebases will instead use await(), which effectively makes it so that the code returns a promise, instead of returning immediately.