Flexible Async API (Callback or Promise)

1.0.2 · active · verified Tue Apr 21

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

Warnings

Install

Imports

Quickstart

This quickstart demonstrates how to create a function that transparently supports both error-first callbacks and Promises using `call-me-maybe`, showing both consumption patterns.

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).');
}

view raw JSON →