Flexible Async API (Callback or Promise)
The `call-me-maybe` package provides a utility function designed to simplify the creation of JavaScript APIs that can gracefully accept either a traditional Node.js-style error-first callback or return a Promise for asynchronous operations. It abstracts away the common boilerplate logic needed to detect the presence of a callback argument, allowing API developers to write their core asynchronous code once. This approach helps in accommodating both classic callback-based consumers and modern Promise-driven codebases without needing to implement separate logic paths or external promisification utilities. The package is currently stable at version `1.0.2` and typically maintains a low release cadence, focusing on stability and bug fixes. Its key differentiator is enabling internal dual-interface support, streamlining the API design process for libraries targeting a broad audience.
Common errors
-
TypeError: callMeMaybe is not a function
cause This error typically occurs when attempting to import `callMeMaybe` as a named export (`import { callMeMaybe } from 'call-me-maybe';`) or incorrectly destructuring in CommonJS (`const { callMeMaybe } = require('call-me-maybe');`). The package uses a default export.fixFor ESM, use `import callMeMaybe from 'call-me-maybe';`. For CommonJS, use `const callMeMaybe = require('call-me-maybe');`. -
ReferenceError: global is not defined
cause This error was a known bug in `call-me-maybe` versions prior to `1.0.2` when used in specific CommonJS environments that did not properly define or polyfill the `global` object.fixUpgrade your `call-me-maybe` package to version `1.0.2` or newer. If this is not possible, ensure your CJS environment explicitly defines `global` if it's expected.
Warnings
- gotcha Older versions of `call-me-maybe` (prior to `v1.0.2`) could encounter `ReferenceError: global is not defined` in certain CommonJS environments. This was due to an implicit reliance on the `global` object that might not be available or correctly polyfilled in all CJS setups.
- gotcha When defining your asynchronous logic within the `callMeMaybe` wrapper, it is crucial to ensure that you always call either `resolve` or `reject` (or the provided callback with `err` or `data`). Failure to do so will result in hanging operations, where Promises never settle and callbacks are never invoked, leading to unresponsive applications.
- gotcha The `callMeMaybe` utility assumes the *last* argument passed to your API function is the potential callback. If your function's signature changes, or if you have optional arguments that could be mistaken for a callback, ensure they are handled correctly before passing to `callMeMaybe` to avoid unexpected behavior.
Install
-
npm install call-me-maybe -
yarn add call-me-maybe -
pnpm add call-me-maybe
Imports
- callMeMaybe
import { callMeMaybe } from 'call-me-maybe';import callMeMaybe from 'call-me-maybe';
- callMeMaybe (CJS)
const { callMeMaybe } = require('call-me-maybe');const callMeMaybe = require('call-me-maybe');
Quickstart
import callMeMaybe from 'call-me-maybe';
interface FetchOptions {
delay?: number;
shouldError?: boolean;
}
/**
* Simulates an asynchronous data fetch that can be consumed via callback or Promise.
*/
function fetchData(id: string, options?: FetchOptions, callback?: (err: Error | null, data?: string) => void): Promise<string> | void {
// callMeMaybe expects the callback as the last argument.
// The second argument is an async function that receives resolve/reject for the Promise path.
return callMeMaybe(callback, async (resolve, reject) => {
try {
const delay = options?.delay ?? 100;
await new Promise(res => setTimeout(res, delay)); // Simulate network delay
if (options?.shouldError || id === 'error') {
throw new Error(`Failed to fetch data for ID: ${id}`);
}
const data = `Successfully fetched data for ID: ${id} (delayed by ${delay}ms)`;
resolve(data);
} catch (error: any) {
reject(error);
}
});
}
// --- Usage Examples ---
// 1. Promise-based consumption
console.log('--- Promise Usage ---');
fetchData('user-promise', { delay: 50 })
.then(data => console.log('Promise resolved:', data))
.catch(err => console.error('Promise rejected:', err.message));
fetchData('error-promise', { shouldError: true, delay: 20 })
.then(data => console.log('Promise resolved (unexpected):', data))
.catch(err => console.error('Promise rejected:', err.message));
// 2. Callback-based consumption
console.log('\n--- Callback Usage ---');
fetchData('user-callback', { delay: 70 }, (err, data) => {
if (err) {
console.error('Callback error:', err.message);
return;
}
console.log('Callback success:', data);
});
fetchData('error-callback', { shouldError: true, delay: 30 }, (err, data) => {
if (err) {
console.error('Callback error:', err.message);
return;
}
console.log('Callback success (unexpected):', data);
});
// 3. Illustrating the return type (for environments where no callback is provided)
console.log('\n--- Mixed Usage (implicitly Promise) ---');
const resultWithoutCallback = fetchData('implicit-promise', { delay: 10 });
if (resultWithoutCallback instanceof Promise) {
resultWithoutCallback
.then(data => console.log('Implicit Promise resolved:', data))
.catch(err => console.error('Implicit Promise rejected:', err.message));
} else {
// This branch is only hit if a callback was provided and handled internally.
console.log('Function returned void (callback handled it).');
}