mrkeyoor.com_
Sat 08 Aug 22:55 UTC
npmUtilsupdated 08 Aug 2026

promise-breaker

promise-breaker is an ESM utility for library authors who must support both Node-style callbacks and Promises through one function. It can wrap callback code so callers may await it, wrap async code so legacy callers may pass a callback, or invoke a user-supplied function without knowing which completion style it uses. Version 7 is a TypeScript rewrite with declarations and no runtime dependencies, aimed mostly at compatibility layers and gradual legacy migrations.

Verdict

Useful when dual callback and Promise support is an actual compatibility requirement, especially for a published library. For application code you control, standardize on async functions and leave this adapter out.

API stability3/5Version 7 keeps legacy make and break aliases, but it is explicitly a complete rewrite: make and break became promisify and callbackify, old promisify options disappeared, automatic function length preservation was removed, and packaging became ESM. The small new surface is understandable, yet the June 2026 major release means users coming from version 6 need deliberate migration tests.
Docs4/5The README explains the migration use case, documents all seven current helpers, and calls out callback detection, variadic-function limits, synchronous throws, and the never-settling Promise created by promisify's callback form. It is detailed enough to work from, though generated API tables contain awkward parameter descriptions and there is no concise version 6 to 7 migration page.
Maintenance5/5The TypeScript rewrite and npm version 7.0.0 were published on June 8, 2026, with the repository pushed the same day. The project has current lint, test, coverage, and GitHub Actions tooling, and GitHub reports only one open issue or pull request. This is recent substantive maintenance, not a metadata-only republish.
Ecosystem4/5npm recorded 3,066,598 downloads in the latest measured week, the repository has 85 stars, and the package handles a common boundary in older Node ecosystems. TypeScript declarations cover arbitrary parameter tuples rather than a short fixed overload list. Its relevance is narrower for new code because Promise-first APIs are now the default and Node already supplies util.promisify and util.callbackify for one-way conversions.

Use it if

  • You publish a library whose established API must accept both callbacks and Promises during a migration
  • You are converting a callback-heavy codebase gradually and cannot update every caller in one release
  • Your extension API accepts user functions that may either return a Promise or call a Node-style callback
  • You want generic TypeScript overloads without hand-writing the same adapter logic for many functions
Skip it if

Setup reality

Install with `npm install promise-breaker` and import its named exports. Version 7 has no runtime dependencies and includes TypeScript declarations, but it is ESM-only because the package declares `type: module` and points its main entry at an ES module. There is no documented Node engine floor, so compatibility must be established in your own test matrix. The harder part is choosing the correct adapter. promisify always appends an internal callback; when a caller already supplied one, a normal fixed-arity implementation uses the caller's callback and ignores the extra one. The JavaScript wrapper still creates and returns a Promise in that branch, but the type overload says void and the unused Promise never settles, so callback callers must ignore the return value. callbackify assumes the last function argument is the callback unless `{ args: number }` tells it how many real arguments to expect. call and apply can accept either callback or Promise implementations, but an implementation that both invokes its callback and returns a Promise can create double-completion surprises in callback mode. None of the adapters add a timeout, cancellation, or protection against a callback that fires twice. Version 7 is also a complete rewrite: make and break remain aliases, while old option shapes and generated function-length behavior were removed. Test both calling styles, error paths, method `this` binding, and any variadic signatures before treating the wrapper as transparent.

Patterns

Offer Promise and callback calls from callback codepromisify-callback-function

import { promisify } from 'promise-breaker';

const readUser = promisify((id, done) => {
  database.get(id, done);
});

const user = await readUser('42');

The wrapped implementation must use a Node-style callback whose first argument is the error and second argument is the result.

Keep a legacy callback caller workingcall-promisified-with-callback

readUser('42', (error, user) => {
  if (error) {
    console.error(error);
    return;
  }
  console.log(user);
});

Ignore the return value in callback form. The implementation creates a Promise that does not settle when the caller's callback occupies the expected callback position.

Add callback support to an async functioncallbackify-async-function

import { callbackify } from 'promise-breaker';

const loadConfig = callbackify(async (file) => {
  const text = await fs.promises.readFile(file, 'utf8');
  return JSON.parse(text);
});

const config = await loadConfig('config.json');

Without a callback, callbackify returns the original Promise from the async implementation.

Disambiguate a real function argumentprotect-function-argument

const mapAsync = callbackify({ args: 2 }, async (items, mapper) => {
  return Promise.all(items.map(mapper));
});

const doubled = await mapAsync([1, 2], (n) => n * 2);

Without `{ args: 2 }`, the wrapper would mistake mapper for the optional completion callback and remove it before calling the implementation.

Await either a callback or Promise implementationcall-unknown-async-style

import { call } from 'promise-breaker';

async function runPlugin(plugin, input) {
  return call(plugin.transform, plugin, input);
}

const output = await runPlugin(plugin, source);

Pass the owning object as thisArg for methods. The function must settle by callback or Promise; no timeout is added if it does neither.

Invoke an unknown-style function with an argument arrayapply-argument-array

import { apply } from 'promise-breaker';

const args = ['report.csv', { encoding: 'utf8' }];
const result = await apply(loader, loaderContext, args);

apply appends a callback to the array. If loader returns a thenable, that returned thenable wins over the internally created callback Promise.

Adapt an unknown-style function to a callback callercall-unknown-with-callback

import { callWithCb } from 'promise-breaker';

callWithCb(plugin.transform, plugin, source, (error, output) => {
  if (error) return done(error);
  save(output, done);
});

Do not pass implementations that both call their callback and return a Promise; callback mode may invoke the completion callback from both paths.

Control callback placement with addPromiseadd-promise-manually

import { addPromise } from 'promise-breaker';

function lookup(id, options, done) {
  return addPromise(done, (finish) => {
    legacyLookup(id, options, finish);
  });
}

const record = await lookup('42', { fresh: true });

Use this lower-level helper for variadic or unusual signatures where promisify's extra trailing callback is unsafe.

Control an async implementation with addCallbackadd-callback-manually

import { addCallback } from 'promise-breaker';

function calculate(input, done) {
  return addCallback(done, async () => {
    const value = await expensiveStep(input);
    return value * 2;
  });
}

When done is supplied, addCallback returns undefined and reports fulfillment or rejection through that callback.

Expose accurate TypeScript overloadsdeclare-typescript-overloads

import { addCallback, type Callback } from 'promise-breaker';

export function sum(a: number, b: number): Promise<number>;
export function sum(a: number, b: number, done: Callback<number>): void;
export function sum(a: number, b: number, done?: Callback<number>) {
  return addCallback(done, () => a + b);
}

The explicit overloads make Promise and callback return types honest to callers; the implementation signature must accept both branches.

Preserve this when wrapping a methodpreserve-method-context

class Store {
  prefix = 'user:';

  load = promisify(function (id, done) {
    backend.get(this.prefix + id, done);
  });
}

const store = new Store();
const user = await store.load('42');

The wrappers call the original function with the wrapper's this value. Extracting store.load into a bare variable still loses that context.

Alternatives

PackageRegistryPick it when
pifynpmUse it when you only need to convert callback functions to Promise-returning functions and do not need a dual-mode public API
universalifynpmUse it for focused fromCallback and fromPromise wrappers that expose callback-or-Promise calling conventions
thenifynpmUse it when converting Node-style callback functions to Promises, including multi-argument callback results