Promisification is the process of converting functions that use callbacks into functions that return promises. This allows using modern syntax like async/await and promise methods (.then, .catch) instead of traditional callback functions. Promisification helps avoid “Callback Hell” and makes code more readable and maintainable.
Key benefits:
Promisification is a technique in JavaScript that allows converting callback-based functions into promise-returning functions. This is especially useful when working with legacy code or libraries that don’t yet use promises.
Promisification solves the “Callback Hell” problem by converting traditional callback functions into promises:
// Traditional callback
function readFileCallback(filename, callback) {
// ... implementation
if (error) {
callback(error, null);
} else {
callback(null, data);
}
}
// Promisified version
function readFilePromise(filename) {
return new Promise((resolve, reject) => {
readFileCallback(filename, (error, data) => {
if (error) {
reject(error);
} else {
resolve(data);
}
});
});
}Many Node.js APIs use the callback approach:
const fs = require('fs');
// Callback version
fs.readFile('file.txt', 'utf8', (error, data) => {
if (error) {
console.error('Error:', error);
} else {
console.log('Data:', data);
}
});
// Promisified version
function readFile(filename, encoding) {
return new Promise((resolve, reject) => {
fs.readFile(filename, encoding, (error, data) => {
if (error) {
reject(error);
} else {
resolve(data);
}
});
});
}
// Usage
readFile('file.txt', 'utf8')
.then(data => console.log('Data:', data))
.catch(error => console.error('Error:', error));Functions with multiple parameters can also be promisified:
// Function with callback
function multiplyWithCallback(a, b, callback) {
setTimeout(() => {
if (typeof a !== 'number' || typeof b !== 'number') {
callback(new Error('Arguments must be numbers'), null);
} else {
callback(null, a * b);
}
}, 1000);
}
// Promisified version
function multiply(a, b) {
return new Promise((resolve, reject) => {
multiplyWithCallback(a, b, (error, result) => {
if (error) {
reject(error);
} else {
resolve(result);
}
});
});
}
// Usage
multiply(5, 3)
.then(result => console.log('Result:', result)) // 15
.catch(error => console.error('Error:', error.message));Node.js provides a built-in way to promisify:
const { promisify } = require('util');
const fs = require('fs');
// Promisification with util.promisify
const readFile = promisify(fs.readFile);
const writeFile = promisify(fs.writeFile);
// Usage
async function processFile() {
try {
const data = await readFile('input.txt', 'utf8');
const processedData = data.toUpperCase();
await writeFile('output.txt', processedData, 'utf8');
console.log('File processed successfully');
} catch (error) {
console.error('Error processing file:', error);
}
}// Callback version
function xhrRequest(url, callback) {
const xhr = new XMLHttpRequest();
xhr.open('GET', url);
xhr.onload = function() {
if (xhr.status === 200) {
callback(null, xhr.responseText);
} else {
callback(new Error(`HTTP error: ${xhr.status}`));
}
};
xhr.onerror = function() {
callback(new Error('Network error'));
};
xhr.send();
}
// Promisified version
function fetchUrl(url) {
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
xhr.open('GET', url);
xhr.onload = function() {
if (xhr.status === 200) {
resolve(xhr.responseText);
} else {
reject(new Error(`HTTP error: ${xhr.status}`));
}
};
xhr.onerror = function() {
reject(new Error('Network error'));
};
xhr.send();
});
}
// Usage
fetchUrl('/api/data')
.then(data => console.log('Data received:', data))
.catch(error => console.error('Error:', error.message));// Callback version
function delayCallback(ms, callback) {
setTimeout(() => {
callback(null, `Passed ${ms} milliseconds`);
}, ms);
}
// Promisified version
function delay(ms) {
return new Promise(resolve => {
setTimeout(() => {
resolve(`Passed ${ms} milliseconds`);
}, ms);
});
}
// Usage with async/await
async function example() {
console.log('Start');
const message = await delay(2000);
console.log(message);
console.log('End');
}// Callback version with multiple results
function getUserWithCallback(id, callback) {
// Simulating async operation
setTimeout(() => {
if (id <= 0) {
callback(new Error('Invalid user ID'), null);
} else {
// Callback receives multiple parameters
callback(null, { id, name: `User ${id}` }, 'extra data');
}
}, 1000);
}
// Promisified version
function getUser(id) {
return new Promise((resolve, reject) => {
getUserWithCallback(id, (error, user, extraData) => {
if (error) {
reject(error);
} else {
// Return object with all data
resolve({ user, extraData });
}
});
});
}
// Usage
getUser(123)
.then(result => {
console.log('User:', result.user);
console.log('Extra data:', result.extraData);
})
.catch(error => console.error('Error:', error.message));// ❌ Improper error handling
function badPromisify(fn) {
return function(...args) {
return new Promise(resolve => {
fn(...args, (error, result) => {
if (error) {
// Forgot reject
console.error(error);
} else {
resolve(result);
}
});
});
};
}
// ✅ Proper error handling
function goodPromisify(fn) {
return function(...args) {
return new Promise((resolve, reject) => {
fn(...args, (error, result) => {
if (error) {
reject(error);
} else {
resolve(result);
}
});
});
};
}// ❌ Missing return
function badPromisify(fn) {
function(...args) { // Missing return
return new Promise((resolve, reject) => {
fn(...args, (error, result) => {
if (error) {
reject(error);
} else {
resolve(result);
}
});
});
}
}
// ✅ Proper return
function goodPromisify(fn) {
return function(...args) {
return new Promise((resolve, reject) => {
fn(...args, (error, result) => {
if (error) {
reject(error);
} else {
resolve(result);
}
});
});
};
}Promisification is an important technique that allows modernizing legacy code using the callback approach and integrating it with modern asynchronous patterns. Understanding promisification helps write cleaner, more readable, and more maintainable code.
Promisify the following function and explain what will be output to the console:
// Original function with callback
function calculateWithCallback(a, b, operation, callback) {
setTimeout(() => {
switch (operation) {
case 'add':
callback(null, a + b);
break;
case 'subtract':
callback(null, a - b);
break;
case 'multiply':
callback(null, a * b);
break;
default:
callback(new Error('Unknown operation'), null);
}
}, 1000);
}
// Your task: create a promisified version
// and execute the following code:
// calculate(10, 5, 'add')
// .then(result => {
// console.log('Addition:', result);
// return calculate(result, 3, 'multiply');
// })
// .then(result => {
// console.log('Multiplication:', result);
// return calculate(result, 7, 'subtract');
// })
// .then(result => {
// console.log('Subtraction:', result);
// })
// .catch(error => {
// console.error('Error:', error.message);
// });Solution:
// Promisified version
function calculate(a, b, operation) {
return new Promise((resolve, reject) => {
calculateWithCallback(a, b, operation, (error, result) => {
if (error) {
reject(error);
} else {
resolve(result);
}
});
});
}
// Code execution
calculate(10, 5, 'add')
.then(result => {
console.log('Addition:', result); // Addition: 15
return calculate(result, 3, 'multiply');
})
.then(result => {
console.log('Multiplication:', result); // Multiplication: 45
return calculate(result, 7, 'subtract');
})
.then(result => {
console.log('Subtraction:', result); // Subtraction: 38
})
.catch(error => {
console.error('Error:', error.message);
});Explanation:
Benefits of this approach:
Want more articles to prepare for interviews? Subscribe to EasyAdvice, bookmark the site and improve yourself every day 💪