- Home
- /
- Tutorials
- /
- JavaScript Tutorial
- /
- JavaScript Async Callbacks
JavaScript Asynchronous
JavaScript Async Callbacks
An asynchronous callback is a function that runs once slow work has finished.
Before promises existed, this was the only way to handle a result that arrives later.
It works, but it nests badly once one job depends on another.
Example
Example
javascript
function loadUser(callback) {
setTimeout(function () {
callback("Ada");
}, 10);
}
loadUser(function (name) {
console.log("got " + name);
});The output is got Ada, once the slow work has finished.
What is an Async Callback?
You pass a function in, and it is called when the work completes.
The result is given to the callback as an argument.
Nothing after the call can use the result, because it has not arrived yet.
Syntax
Syntax
javascript
function load(callback) {
// later:
callback(result);
}The callback is called by the function, not by you.
The Result Only Exists Inside
Trying to return it does not work, because the function finishes first.
Example
Example
javascript
function loadUser(callback) {
setTimeout(function () {
callback("Ada");
}, 10);
}
const result = loadUser(function () {});
console.log(typeof result);The output is undefined: there was nothing to return.
The Error-First Pattern
By convention the first argument is an error, or null when all went well.
This is how Node.js callbacks have always worked.
Example
Example
javascript
function loadUser(callback) {
setTimeout(function () {
callback(null, "Ada");
}, 10);
}
loadUser(function (error, name) {
if (error) {
console.log("failed");
return;
}
console.log("loaded " + name);
});Checking the error first means the happy path stays clear.
Reporting a Failure
The same callback handles both outcomes.
Example
Example
javascript
function loadUser(callback) {
setTimeout(function () {
callback("user not found");
}, 10);
}
loadUser(function (error, name) {
console.log(error ? "error: " + error : "loaded " + name);
});The output is error: user not found.
Callback Hell
When one job needs the result of the last, the nesting grows sideways.
Three or four levels in, this becomes very hard to follow.
Example
Example
javascript
function step(name, callback) {
setTimeout(function () {
callback(name);
}, 5);
}
step("one", function (a) {
step("two", function (b) {
step("three", function (c) {
console.log(a + " " + b + " " + c);
});
});
});Promises exist mainly to flatten this shape out.
Named Functions Help a Little
Pulling each level out reduces the nesting, but not the awkwardness.
Example
Example
javascript
function step(name, callback) {
setTimeout(function () {
callback(name);
}, 5);
}
function afterSecond(b) {
console.log("finished with " + b);
}
function afterFirst(a) {
step("two", afterSecond);
}
step("one", afterFirst);The real fix is promises, which the next lesson covers.
Complete Example
Complete Example
html
<!DOCTYPE html>
<html>
<head>
<title>JavaScript Async Callbacks</title>
</head>
<body>
<h1>Async Callbacks</h1>
<p id="out">Loading...</p>
<script>
function loadUser(callback) {
setTimeout(function () {
callback(null, "Ada Lovelace");
}, 1000);
}
loadUser(function (error, name) {
const out = document.getElementById("out");
out.textContent = error ? "Failed: " + error : "Welcome, " + name;
});
</script>
</body>
</html>Try It Yourself
Run the above example in the Try It Editor.
Try an error:
Change the call to callback("no network") and see the other branch run.
Important Points
- A callback runs when the slow work has finished.
- The result cannot be returned, only passed to the callback.
- The error-first pattern puts the error in the first argument.
- Nesting callbacks quickly becomes hard to read.
- Promises were created to solve exactly this problem.
Conclusion
Callbacks are the oldest way of handling asynchronous results, and still everywhere.
Understanding them explains why promises look the way they do.
The pain of nesting is what the next few lessons remove.
