You've already used callbacks several times in this course — fs.readFile()'s last argument, every listener passed to on(). This lesson makes the pattern explicit and names the problem that led to promises, covered next.
A callback is nothing special — it's just a function passed as an argument to another function, to be called later instead of immediately.
function processOrder(item, callback) {
console.log(`Processing order: ${item}`)
callback(item)
}
processOrder('Laptop Stand', (item) => {
console.log(`Done processing: ${item}`)
})Node's own built-in async functions all follow one convention: the callback's first parameter is always an error (or null if nothing went wrong), and the actual result comes after it.
fs.readFile('data.txt', 'utf8', (err, data) => {
if (err) {
console.error('Something went wrong:', err)
return
}
console.log(data)
})Checking err first, before touching data, is the convention every Node callback follows — skip the check and a failed read crashes the program on the next line instead of failing gracefully.
One async step calling another quickly nests:
fs.readFile('user.json', 'utf8', (err, userData) => {
if (err) return console.error(err)
fs.readFile('settings.json', 'utf8', (err, settingsData) => {
if (err) return console.error(err)
fs.readFile('preferences.json', 'utf8', (err, prefData) => {
if (err) return console.error(err)
console.log('All data loaded')
})
})
})Three steps deep and this is already hard to follow — each step is indented inside the last, error handling repeats at every level, and adding a fourth step means nesting even further. This shape earned its own nickname: callback hell. It doesn't get better with scale; it gets worse.
This is the exact problem Promises were invented to solve — flattening this pyramid back into something that reads top to bottom. The next lesson picks up right here.