How to turn a callback function into a promise
Short answer
Create a new Promise and call resolve on success and reject on error inside the callback:
function loadData(id, callback) {
setTimeout(() => {
if (id < 0) callback(new Error('Invalid id'));
else callback(null, { id, title: 'Report' });
}, 50);
}
const loadDataAsync = (id) =>
new Promise((resolve, reject) => {
loadData(id, (error, data) => (error ? reject(error) : resolve(data)));
});
const data = await loadDataAsync(7);
console.log(data.title);
try {
await loadDataAsync(-1);
} catch (error) {
console.log(error.message);
}Output:
Report
Invalid idAn image in the browser
Events like load and error turn into a promise the same way:
const loadImage = (src) =>
new Promise((resolve, reject) => {
const image = new Image();
image.onload = () => resolve(image);
image.onerror = () => reject(new Error(`Could not load ${src}`));
image.src = src;
});
const logo = await loadImage('/logo.png');
document.body.append(logo);In Node.js
Node.js has a ready helper promisify() for functions with a callback in the style of (error, result):
import { promisify } from 'node:util';
function add(a, b, callback) {
setTimeout(() => callback(null, a + b), 10);
}
const addAsync = promisify(add);
console.log(await addAsync(2, 3));Output:
5Common mistake
An error thrown inside a timer callback does not reach reject() by itself. Catch it and call reject() yourself, otherwise the promise waits forever.
const parseLater = (text) =>
new Promise((resolve, reject) => {
setTimeout(() => {
try {
resolve(JSON.parse(text));
} catch (error) {
reject(error);
}
}, 10);
});
console.log(await parseLater('{bad').catch((error) => error.name));Output:
SyntaxErrorFunctions used in this recipe
- Promise — A promise of a result that will be ready later: success or failure.
- async function — A function that always returns a promise and lets you use await inside.