assert.ifError()

Throws the given value unless it is null or undefined, for checking callback error arguments.

Signature
assert.ifError(value)
  • Added in Node.js v0.1.97

Parameters

ParameterTypeDescription
$valueanyThe value to check, normally the error argument of an error-first callback. Only null and undefined pass.

Errors

Throws an AssertionError with code ERR_ASSERTION for any value other than null or undefined, with the original value exposed as the actual property.

Changelog

  • PHP v10.0.0Instead of throwing the original error it is now wrapped into an AssertionError that contains the full stack trace.
  • PHP v10.0.0Value may now only be undefined or null. Before, all falsy values were handled the same as null and did not throw.

assert.ifError() exists for one shape of code: the error-first callback. It turns if (err) throw err; into a single expression that also preserves the stack.

It is not a truthiness check

Since Node 10 the rule is only null and undefined pass. Every other value throws, including the falsy ones — assert.ifError(0), assert.ifError('') and assert.ifError(false) all raise an AssertionError. Before v10 those were treated like null. If you are checking an exit code or a count, this is the wrong function; use assert.strictEqual().

The wrapping is the feature

Also since v10, the original error is not re-thrown directly. It is wrapped in an AssertionError whose stack contains both the frames from the original error and the frames leading to the ifError() call. In async callback code the original stack usually starts inside libuv and tells you nothing about which of your calls failed; the wrapper is what makes the failure locatable. The original value stays reachable as err.actual, so you can still assert on its code.

Callbacks only

Promise-based APIs signal failure by rejecting, which is assert.rejects()'s job.

Example

Try it yourself
Loading editor…
Press Run to execute the code.

When to use it

  • Fail fast on the err argument of an fs, dns or database callback inside a test
  • Convert a callback error into a located AssertionError instead of an opaque libuv stack
  • Assert a legacy callback API succeeded before inspecting the value it returned

Frequently asked questions

Does assert.ifError() only throw for truthy values?

No — since Node 10 it throws for any value that is not null or undefined, so assert.ifError(0), assert.ifError('') and assert.ifError(false) all raise an AssertionError. Before v10 all falsy values were treated like null and passed.

What is the difference between assert.ifError(err) and if (err) throw err?

ifError wraps the value in an AssertionError whose stack includes both the original error's frames and the callsite of the assertion, which is what makes an async callback failure locatable. It also rejects falsy non-null values that a plain if (err) check would let through.

How do I get the original error back after assert.ifError() throws?

Read the actual property of the thrown AssertionError — it holds the value you passed in, so wrapped.actual.code gives you the original ENOENT or ENOTFOUND. The message also embeds the original error's text.

More examples

Falsy values still throw

Every falsy value other than null and undefined throws, which is the behaviour change made in Node 10 and the reason code written against Node 8 can start failing. The last entry prints as null because JSON.stringify(NaN) is 'null', while the message itself shows the real value.

Example · javascript
const assert = require('node:assert');

// Only these two pass.
assert.ifError(null);
assert.ifError(undefined);
console.log('null and undefined pass');

for (const value of [0, '', false, NaN]) {
  try {
    assert.ifError(value);
    console.log(JSON.stringify(value), 'passed');
  } catch (err) {
    console.log(JSON.stringify(value), '->', err.message);
  }
}
// 0 -> ifError got unwanted exception: 0
// "" -> ifError got unwanted exception: ''
// false -> ifError got unwanted exception: false
// null -> ifError got unwanted exception: NaN

// For a numeric check, use strictEqual instead.
assert.strictEqual(0, 0);
console.log('use strictEqual for exit codes and counts');

Why the wrapped stack matters

The raw callback error's stack points into node:dns and gives no clue which lookup failed, while the AssertionError that ifError builds carries the frames from your own callsite. The original error is still available as .actual, so assertions on its code keep working.

Example · javascript
const assert = require('node:assert');
const dns = require('node:dns');

dns.lookup('does-not-resolve.invalid', (err) => {
  // Without ifError: the raw error's stack starts in libuv internals.
  console.log('raw stack head:', String(err.stack).split('\n')[1].trim());
  // at GetAddrInfoReqWrap.onlookup [as oncomplete] (node:dns:120:26)

  try {
    assert.ifError(err);
  } catch (wrapped) {
    // The AssertionError stack includes THIS file and this callsite.
    console.log('wrapped code:', wrapped.code);        // ERR_ASSERTION
    console.log('original code:', wrapped.actual.code); // ENOTFOUND
    console.log('stack mentions this file:', wrapped.stack.includes(__filename));
  }
});

Inside the built-in test runner

ifError is the first line inside the callback so an unexpected I/O failure fails the test at that point rather than producing a confusing TypeError from reading cfg.port on undefined. The unlink runs before the assertions so the fixture is removed even when one of them throws.

Example · javascript
const test = require('node:test');
const assert = require('node:assert/strict');
const fs = require('node:fs');

// A legacy callback-style API under test.
function readJson(path, cb) {
  fs.readFile(path, 'utf8', (err, text) => {
    if (err) return cb(err);
    let parsed;
    try { parsed = JSON.parse(text); } catch (e) { return cb(e); }
    cb(null, parsed);
  });
}

test('readJson parses a file', (t, done) => {
  fs.writeFileSync('./fixture.json', '{"port":8080}');
  readJson('./fixture.json', (err, cfg) => {
    fs.unlinkSync('./fixture.json');
    assert.ifError(err);
    assert.deepStrictEqual(cfg, { port: 8080 });
    done();
  });
});

// $ node --test
// ✓ readJson parses a file (2.0ms)

Discussion

  • Be the first to comment on this lesson.