Let your JS API users either give you a callback or receive a promise
—
Call 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 statementInstall with Tessl CLI
npx tessl i tessl/npm-call-me-maybe