While Promises and async/await are increasingly the primary way to write asynchronous code in JavaScript, callbacks are still used in many places. Several libraries that adopted that style are slow to migrate to the more modern alternative, and browser (and Node) APIs are also slow to change.
For example, marked, a markdown compiler needs a callback when it's used in asynchronous mode:
marked(text, options, (err, result) => {
// result is the compiled markdown
});
Similarly, setTimeout
invokes a function when the time is up:
setTimeout(callback, 100);
Not to mention a ton of web APIs, such as Indexed DB, FileReader, and others. Callbacks are still everywhere, and it's a good practice to convert them to Promises especially if your code is already using async/await.
Callbacks implement the continuation-passing style programming where a function instead of returning a value calls a continuation, in this case, an argument function. It is especially prevalent in JavaScript as it does not support synchronous waiting. Everything that involves some future events, such as network calls, an asynchronous API, or a simple timeout is only possible by using a callback mechanism.
There are several ways callbacks can work. For example, setTimeout
uses a callback-first pattern:
setTimeout(callback, ms);
Or functions can get multiple functions and call them when appropriate:
const checkAdmin = (id, isAdmin, notAdmin) => {
if (/* admin logic */) {
isAdmin();
}else {
notAdmin();
}
};
How the callback is invoked can also vary. For example, it might get multiple arguments:
const getUserData = (id, cb) => {
const user = /* get user */
cb(user.id, user.profile, user.avatar);
};
Also, asynchronicity might be implemented as an object that emits events:
const reader = new FileReader();
reader.onload = (event) => {
// event.target.result
}
reader.onerror = (error) => {
// handle error
};
reader.readAsDataURL(blob);