Промисификация — это процесс преобразования функций, использующих обратные вызовы (callback), в функции, возвращающие промисы. Это позволяет использовать современный синтаксис async/await и методы промисов (.then, .catch) вместо традиционных callback-функций. Промисификация помогает избежать “ада обратных вызовов” и делает код более читаемым и поддерживаемым.
Основные преимущества:
Промисификация — это техника в JavaScript, которая позволяет преобразовывать функции, основанные на обратных вызовах, в функции, возвращающие промисы. Это особенно полезно при работе с устаревшим кодом или библиотеками, которые еще не используют промисы.
Промисификация решает проблему “Callback Hell” (ад обратных вызовов), преобразуя традиционные callback-функции в промисы:
// Традиционный callback
function readFileCallback(filename, callback) {
// ... реализация
if (error) {
callback(error, null);
} else {
callback(null, data);
}
}
// Промисифицированная версия
function readFilePromise(filename) {
return new Promise((resolve, reject) => {
readFileCallback(filename, (error, data) => {
if (error) {
reject(error);
} else {
resolve(data);
}
});
});
}Многие Node.js API используют callback-подход:
const fs = require('fs');
// Callback-версия
fs.readFile('file.txt', 'utf8', (error, data) => {
if (error) {
console.error('Ошибка:', error);
} else {
console.log('Данные:', data);
}
});
// Промисифицированная версия
function readFile(filename, encoding) {
return new Promise((resolve, reject) => {
fs.readFile(filename, encoding, (error, data) => {
if (error) {
reject(error);
} else {
resolve(data);
}
});
});
}
// Использование
readFile('file.txt', 'utf8')
.then(data => console.log('Данные:', data))
.catch(error => console.error('Ошибка:', error));Функции с несколькими параметрами также можно промисифицировать:
// Функция с callback
function multiplyWithCallback(a, b, callback) {
setTimeout(() => {
if (typeof a !== 'number' || typeof b !== 'number') {
callback(new Error('Аргументы должны быть числами'), null);
} else {
callback(null, a * b);
}
}, 1000);
}
// Промисифицированная версия
function multiply(a, b) {
return new Promise((resolve, reject) => {
multiplyWithCallback(a, b, (error, result) => {
if (error) {
reject(error);
} else {
resolve(result);
}
});
});
}
// Использование
multiply(5, 3)
.then(result => console.log('Результат:', result)) // 15
.catch(error => console.error('Ошибка:', error.message));Node.js предоставляет встроенный способ промисификации:
const { promisify } = require('util');
const fs = require('fs');
// Промисификация с помощью util.promisify
const readFile = promisify(fs.readFile);
const writeFile = promisify(fs.writeFile);
// Использование
async function processFile() {
try {
const data = await readFile('input.txt', 'utf8');
const processedData = data.toUpperCase();
await writeFile('output.txt', processedData, 'utf8');
console.log('Файл успешно обработан');
} catch (error) {
console.error('Ошибка при обработке файла:', error);
}
}// Callback-версия
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 ошибка: ${xhr.status}`));
}
};
xhr.onerror = function() {
callback(new Error('Ошибка сети'));
};
xhr.send();
}
// Промисифицированная версия
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 ошибка: ${xhr.status}`));
}
};
xhr.onerror = function() {
reject(new Error('Ошибка сети'));
};
xhr.send();
});
}
// Использование
fetchUrl('/api/data')
.then(data => console.log('Данные получены:', data))
.catch(error => console.error('Ошибка:', error.message));// Callback-версия
function delayCallback(ms, callback) {
setTimeout(() => {
callback(null, `Прошло ${ms} миллисекунд`);
}, ms);
}
// Промисифицированная версия
function delay(ms) {
return new Promise(resolve => {
setTimeout(() => {
resolve(`Прошло ${ms} миллисекунд`);
}, ms);
});
}
// Использование с async/await
async function example() {
console.log('Начало');
const message = await delay(2000);
console.log(message);
console.log('Конец');
}// Callback-версия с несколькими результатами
function getUserWithCallback(id, callback) {
// Имитация асинхронной операции
setTimeout(() => {
if (id <= 0) {
callback(new Error('Неверный ID пользователя'), null);
} else {
// Callback получает несколько параметров
callback(null, { id, name: `Пользователь ${id}` }, 'дополнительные данные');
}
}, 1000);
}
// Промисифицированная версия
function getUser(id) {
return new Promise((resolve, reject) => {
getUserWithCallback(id, (error, user, extraData) => {
if (error) {
reject(error);
} else {
// Возвращаем объект со всеми данными
resolve({ user, extraData });
}
});
});
}
// Использование
getUser(123)
.then(result => {
console.log('Пользователь:', result.user);
console.log('Доп. данные:', result.extraData);
})
.catch(error => console.error('Ошибка:', error.message));// ❌ Неправильная обработка ошибок
function badPromisify(fn) {
return function(...args) {
return new Promise(resolve => {
fn(...args, (error, result) => {
if (error) {
// Забыли reject
console.error(error);
} else {
resolve(result);
}
});
});
};
}
// ✅ Правильная обработка ошибок
function goodPromisify(fn) {
return function(...args) {
return new Promise((resolve, reject) => {
fn(...args, (error, result) => {
if (error) {
reject(error);
} else {
resolve(result);
}
});
});
};
}// ❌ Забытый return
function badPromisify(fn) {
function(...args) { // Забыли return
return new Promise((resolve, reject) => {
fn(...args, (error, result) => {
if (error) {
reject(error);
} else {
resolve(result);
}
});
});
}
}
// ✅ Правильный return
function goodPromisify(fn) {
return function(...args) {
return new Promise((resolve, reject) => {
fn(...args, (error, result) => {
if (error) {
reject(error);
} else {
resolve(result);
}
});
});
};
}Промисификация — это важная техника, которая позволяет модернизировать устаревший код, использующий callback-подход, и интегрировать его с современными асинхронными паттернами. Понимание промисификации помогает писать более чистый, читаемый и поддерживаемый код.
Промисифицируйте следующую функцию и объясните, что будет выведено в консоль:
// Исходная функция с 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('Неизвестная операция'), null);
}
}, 1000);
}
// Ваша задача: создать промисифицированную версию
// и выполнить следующий код:
// calculate(10, 5, 'add')
// .then(result => {
// console.log('Сложение:', result);
// return calculate(result, 3, 'multiply');
// })
// .then(result => {
// console.log('Умножение:', result);
// return calculate(result, 7, 'subtract');
// })
// .then(result => {
// console.log('Вычитание:', result);
// })
// .catch(error => {
// console.error('Ошибка:', error.message);
// });Решение:
// Промисифицированная версия
function calculate(a, b, operation) {
return new Promise((resolve, reject) => {
calculateWithCallback(a, b, operation, (error, result) => {
if (error) {
reject(error);
} else {
resolve(result);
}
});
});
}
// Выполнение кода
calculate(10, 5, 'add')
.then(result => {
console.log('Сложение:', result); // Сложение: 15
return calculate(result, 3, 'multiply');
})
.then(result => {
console.log('Умножение:', result); // Умножение: 45
return calculate(result, 7, 'subtract');
})
.then(result => {
console.log('Вычитание:', result); // Вычитание: 38
})
.catch(error => {
console.error('Ошибка:', error.message);
});Объяснение:
Преимущества такого подхода:
Хотите больше статей для подготовки к собеседованиям? Подписывайтесь на EasyAdvice, добавляйте сайт в закладки и совершенствуйтесь каждый день 💪