Three Flavors of the fs API

Node ships the same operations three times over. node:fs/promises returns promises, node:fs exports error-first callback functions, and the same node:fs module exports blocking ...Sync variants. Each has a job.

The same read through all three fs interfacesJavaScript
import { readFile } from 'node:fs/promises';
import { readFile as readFileCb, readFileSync } from 'node:fs';
const text = await readFile('package.json', 'utf8');        // 1. promise flavor
console.log('promise :', JSON.parse(text).name);
readFileCb('package.json', 'utf8', (err, data) => {         // 2. error-first callback
  if (err) throw err;
  console.log('callback:', JSON.parse(data).name);
});
const raw = readFileSync('package.json', 'utf8');           // 3. blocking
console.log('sync    :', JSON.parse(raw).name);
Output
promise : demo
sync    : demo
callback: demo

Look at the order. The synchronous line is written last and prints second, because it never yields; the callback was registered first and prints last, because it waits for a turn of the event loop.

Use promises by default. Use callbacks in a measured hot path, where they save a promise per call, or inside code that already speaks callbacks — streams and older libraries. Use the synchronous flavor only before your server starts listening, in build tools and in tests, never in a request handler, where it freezes every connection your process is holding.

Whichever flavor you pick, keep fs.exists() out of it — the module's one formally deprecated member (Stability 0), because its callback has no error argument. Ask for the operation you want and handle the failure.