Let your JS API users either give you a callback or receive a promise
npx @tessl/cli install tessl/npm-call-me-maybe@1.0.0Call Me Maybe enables JavaScript APIs to support both callback and promise-based patterns simultaneously. It allows library authors to write promise-based code while automatically providing callback support for users who prefer the traditional error-first callback pattern.
npm install call-me-maybeconst maybe = require("call-me-maybe");ES6 Modules (with bundlers or Node.js ES module support):
import maybe from "call-me-maybe";const maybe = require("call-me-maybe");
// Use in your API function
function asyncFunc(cb) {
return maybe(cb, new Promise((resolve, reject) => {
// Your async logic here
setTimeout(() => resolve("result"), 100);
}));
}
// Users can call with callback
asyncFunc((err, result) => {
if (err) console.error(err);
else console.log(result); // "result"
});
// Or without callback to get promise
asyncFunc()
.then(result => console.log(result)) // "result"
.catch(err => console.error(err));The core functionality that enables APIs to support both callback and promise patterns.
/**
* Provides dual callback/promise interface for async functions
* @param {Function|null|undefined} cb - Optional error-first callback function
* @param {Promise} promise - Promise to handle
* @returns {undefined|Promise} Returns undefined when callback provided, otherwise returns the promise
*/
function maybe(cb, promise);Behavior:
cb is truthy: Executes the promise and calls the callback with error-first parameters
cb(null, result)cb(err)undefinedcb is falsy (null, undefined, false, etc.): Returns the original promise directlyUsage Examples:
Callback pattern:
const maybe = require("call-me-maybe");
function fetchData(cb) {
return maybe(cb, fetch("/api/data").then(res => res.json()));
}
// With callback
fetchData((err, data) => {
if (err) {
console.error("Error:", err);
} else {
console.log("Data:", data);
}
});Promise pattern:
// Without callback - returns promise
fetchData()
.then(data => console.log("Data:", data))
.catch(err => console.error("Error:", err));
// Or with async/await
try {
const data = await fetchData();
console.log("Data:", data);
} catch (err) {
console.error("Error:", err);
}Complex example with error handling:
function processFile(filename, cb) {
const filePromise = fs.promises.readFile(filename, 'utf8')
.then(content => content.toUpperCase())
.then(processed => ({ filename, content: processed }));
return maybe(cb, filePromise);
}
// Callback style with error handling
processFile('data.txt', (err, result) => {
if (err) {
if (err.code === 'ENOENT') {
console.log('File not found');
} else {
console.error('Unexpected error:', err);
}
} else {
console.log(`Processed ${result.filename}: ${result.content}`);
}
});
// Promise style
processFile('data.txt')
.then(result => console.log(`Processed ${result.filename}: ${result.content}`))
.catch(err => {
if (err.code === 'ENOENT') {
console.log('File not found');
} else {
console.error('Unexpected error:', err);
}
});Call Me Maybe uses intelligent async scheduling to ensure callbacks are called asynchronously, preventing potential issues with synchronous callback execution:
process.nextTick in Node.js environmentssetImmediate if available (browser with setImmediate polyfill)setTimeout(fn, 0) as final optionThis ensures consistent asynchronous behavior across all environments.
undefined regardless of promise stateprocess.nextTick schedulingrequire()import statement