Suggest an editImprove this articleRefine the answer for “What is error-first callback pattern?”. Your changes go to moderation before they’re published.Approval requiredContentWhat you’re changing🇺🇸EN🇺🇦UAPreviewTitle (EN)Short answer (EN)**Error-first callback pattern** is a Node.js convention where async functions pass `null` or an `Error` as the first callback argument, and result data as the second. `fs.readFile(path, (err, data) => { if (err) return; })`. Always check `err` before using `data`, and always `return` after handling the error.Shown above the full answer for quick recall.Answer (EN)Image**Error-first callback pattern** - a Node.js convention where async functions always pass an error as the first argument to their callback, followed by result data if no error occurred. ## Theory ### TL;DR - Think of it like a delivery driver: first they report if there was a problem (error), then hand over the package (data) only if everything went fine - Convention: `callback(err, data)` - error first, data second - On success: `err` is `null`, `data` holds the result; on failure: `err` is an `Error` object and `data` is `undefined` - Always check `if (err)` before touching `data`, and always `return` after - skipping the return is the #1 bug - Use for `fs`, `mysql`, `pg` and similar libs; prefer Promises for new code ### Quick example ```javascript const fs = require('fs'); fs.readFile('config.json', 'utf8', (err, content) => { if (err) { console.error('Failed to read:', err.message); // ENOENT: no such file... return; // Critical - stops execution here } console.log(content); // { port: 3000 } - only runs if no error }); ``` Error arrives first. If the file is missing, `err` is set and `content` is `undefined`. The `return` keeps the rest of the callback from running with bad data. ### Why error comes first In synchronous code, you throw an exception and a try-catch somewhere up the stack catches it. That doesn't work for async code. The callback runs later, in a different turn of the event loop, so the original try-catch is long gone. Putting the error first forces you to handle it manually, every time. There is no way to accidentally skip it. ### When to use - Reading or writing files with `fs`: use error-first callbacks - Database queries with `mysql` or `pg`: both follow this convention - Writing a custom async function: pass `(err, result)` to match Node stdlib - Wrapping a legacy third-party lib: error-first keeps things consistent - New code on Node 10+: prefer `fs.promises` and async/await instead ### How Node.js handles it internally libuv threads manage I/O operations like `fs.readFile`. When the op finishes, libuv passes a C-level error code (for example, `UV_ENOENT`) or zero back to V8. The event loop then calls your callback with either an `Error` built from that code, or `null`. The pattern itself is a convention - nothing in the runtime enforces it, which is exactly why forgetting `return` is such a common bug. ### Common mistakes **Forgetting `return` after handling the error** ```javascript // Wrong - falls through to data block fs.readFile('bad.txt', (err, data) => { if (err) console.error(err); // No return! console.log(data); // Runs anyway - logs undefined }); // Right fs.readFile('bad.txt', (err, data) => { if (err) { console.error(err); return; } console.log(data); }); ``` Without `return`, execution continues with `data === undefined`. I've seen this cause silent writes of `undefined` to production databases - the code appears to work but corrupts records. **Swapped argument order in a custom function** ```javascript // Wrong - breaks every caller function getUser(id, cb) { db.query(sql, [id], (err, row) => cb(row, err)); // swapped! } // Right function getUser(id, cb) { db.query(sql, [id], (err, row) => cb(err, row)); } ``` Every Node.js lib expects `(err, data)`. Swap the arguments and callers end up checking the wrong one. **Wrapping async code in try-catch** ```javascript // Does nothing useful try { fs.readFile('file.txt', (err, data) => { throw new Error('boom'); // Not caught by the outer try-catch }); } catch (e) { console.error(e); // Never runs } ``` The try-catch exits before the callback fires. Handle errors inside the callback. ### Real-world usage - `fs.readFile`, `fs.writeFile`: Node.js core, this pattern since v0.1.90 - `mysqljs/mysql`: `connection.query(sql, params, (err, rows) => {})` - over 10M weekly downloads - `pg` (Postgres): `client.query(sql, (err, result) => {})` - standard in most Node backends - Express: the `(err, req, res, next)` error middleware signature extends this convention - Converting to Promise: `util.promisify(fs.readFile)('file.txt').then(...).catch(...)` ### Follow-up questions **Q:** Why doesn't Node.js just throw exceptions for async errors? **A:** Async callbacks run after the original call stack is gone. A thrown exception inside a callback has nowhere to be caught and crashes the process. Error-first keeps control inside the callback. **Q:** How do you convert an error-first callback to a Promise? **A:** Use `util.promisify`: `const readFile = util.promisify(fs.readFile)`. Then call it with `.then().catch()` or inside async/await. **Q:** What if you need to return multiple results? **A:** Pass an object or array as the second argument: `cb(null, { user, token })`. One error, one result - pack multiple values together. **Q:** In a chain of nested callbacks, how do you avoid writing `if (err) return cb(err)` in every step? **A:** Extract each step into a named function, or use the `async` library's `waterfall`. For new code, async/await solves this with a single try-catch over the whole chain. ## Examples ### Basic: file read with error propagation ```javascript const fs = require('fs'); function readConfig(path, cb) { fs.readFile(path, 'utf8', (err, content) => { if (err) return cb(err); // Propagate error up cb(null, JSON.parse(content)); // Pass parsed data on success }); } readConfig('config.json', (err, config) => { if (err) { console.error('Config load failed:', err.message); // ENOENT... return; } console.log('Port:', config.port); // Port: 3000 }); ``` `cb(err)` propagates errors without hiding them. `cb(null, result)` signals success. The caller handles both paths. ### Intermediate: Postgres query in Express ```javascript const express = require('express'); const pg = require('pg'); const app = express(); app.get('/user/:id', (req, res) => { const client = new pg.Client(); client.connect((err) => { if (err) return res.status(500).json({ error: err.message }); client.query( 'SELECT * FROM users WHERE id = $1', [req.params.id], (err, result) => { client.end(); if (err) return res.status(500).json({ error: err.message }); res.json(result.rows[0]); // { id: 1, name: 'Alice' } } ); }); }); ``` Each async step checks `err` before going further. `return` on each error path prevents sending two responses. This nesting pattern is what eventually pushed the community toward Promises.For the reviewerNote to the moderator (optional)Visible only to the moderator. Helps review go faster.