Deeply Nested Callbacks

ID

javascript.nested_callbacks

Severity

low

Remediation Complexity

medium

Remediation Risk

low

Remediation Effort

medium

Resource

Complexity

Language

JavaScript

Tags

async, code-style, complexity

Description

Reports a callback function (a function literal passed as an argument) whose nesting depth exceeds the configured threshold (default 5). The depth counts callback ancestors:

fetch(url, (res) =>
  parse(res, (data) =>
    transform(data, (out) =>
      persist(out, (id) =>
        notify(id, (ok) =>
          cleanup(ok, () => {                  // FLAW (depth 6 with limit 5)
            // ...
          }))))))

Rationale

Deeply nested callbacks — the original "callback hell" — are difficult to read, hard to error-handle correctly (the wrong callback receives the wrong error), and hard to refactor without breaking control flow. Modern code has two clean fixes:

  • async/await, which flattens a Promise chain into linear code,

  • extracting named callback helpers, which compresses the visible nest into a series of well-named function calls.

Remediation

Use async/await:

async function pipeline() {
  const res = await fetch(url);
  const data = await parse(res);
  const out  = await transform(data);
  const id   = await persist(out);
  const ok   = await notify(id);
  await cleanup(ok);
}

Or extract:

fetch(url, onFetched);

function onFetched(res) { parse(res, onParsed); }
function onParsed(data) { transform(data, onTransformed); }
// ...

Notes

The threshold is configurable via the maxNesting rule property. A callback is identified as a function literal in an argument position; function declarations and methods don’t count. The depth includes the current callback (so maxNesting: 5 allows up to 5 nested callbacks; the 6th fires).