Що таке promisification?
Promisification - це перетворення функції з колбек-стилем на функцію, яка повертає Promise. Замість вкладених колбеків отримуєш .then(), .catch() або async/await.
Теорія
TL;DR
- Колбеки - це як записка другу: "зателефонуй, коли зробиш." Promisification загортає це в запит з чітким результатом: або успіх, або помилка.
- Головна різниця: замість
(err, result) => {}отримуєш.then(result => {}).catch(err => {}). - Використовуй для старих Node.js API (
fs.readFile,crypto) і сторонніх бібліотек на колбеках. Якщо API вже повертає Promise - не потрібно. util.promisifyавтоматично розпізнає стандартний Node.js патерн(err, value).
Швидкий приклад
const fs = require('fs');
const util = require('util');
// Оригінальний колбек-стиль
fs.readFile('data.txt', 'utf8', (err, data) => {
if (err) return console.error(err);
console.log(data); // Output: вміст файлу
});
// Promisified - той самий результат, без вкладеності
const readFile = util.promisify(fs.readFile);
const data = await readFile('data.txt', 'utf8');
console.log(data); // Output: вміст файлуРезультат однаковий. Promisified-версія дозволяє використати await і обробити помилки одним try/catch.
Ключова різниця
Стандартний колбек-патерн у Node.js очікує (err, result) останнім аргументом. Promisification загортає таку функцію в нову, яка створює Promise всередині, передає згенерований колбек оригіналу і або викликає resolve(result), або reject(err). Пишеш обгортання один раз і використовуєш скрізь.
Коли застосовувати
- Вбудовані Node.js API (
fs,crypto,dns) до появи Promises: promisify і переходь наasync/await. - Сторонні бібліотеки з колбек-стилем: огорни один раз, використовуй ланцюжком скрізь.
- Легасі-код, який не можна переписати: promisify інтерфейс, залиш внутрішню логіку без змін.
- API вже повертає Promise: пропускай.
Як це працює всередині
util.promisify (доданий у Node.js v8.0.0) перевіряє, чи є у функції символ util.promisify.custom. Якщо ні - вважає, що остання функція в аргументах це колбек формату (err, value). Повертає обгортку, яка створює new Promise, викликає оригінальну функцію зі згенерованим колбеком і або resolve(value), або reject(err).
Типові помилки
Помилка: promisify функції, яка не відповідає Node.js error-first стилю.
const sleep = util.promisify(setTimeout); // TypeError: callback is not a functionsetTimeout приймає простий колбек, не (err, value). Замість цього: new Promise(resolve => setTimeout(resolve, 1000)).
Помилка: втрата this при promisify методів класу.
class DB {
read(id, cb) { cb(null, 'data'); }
}
const db = new DB();
const read = util.promisify(db.read); // this = undefined всередині readВиправлення: util.promisify(db.read.bind(db)). Прив'язуй екземпляр перед огортанням. Ця помилка частіше призводить до непомітних збоїв у продакшені, ніж неправильна сигнатура колбека.
Помилка: очікування одного значення, коли оригінал передає кілька.
const lookup = util.promisify(dns.lookup);
lookup('example.com').then(address => console.log(address)); // неправильно - address це [address, family]dns.lookup викликає колбек з (err, address, family). util.promisify резолвить у масив [address, family]. Деструктуруй: .then(([address, family]) => ...).
Де зустрічається на практиці
- Express:
util.promisify(fs.access)перед віддачею статичних файлів. - MongoDB callback driver:
util.promisify(collection.findOne)у старих Node.js застосунках і Lambda-функціях. - AWS SDK v2:
util.promisify(s3.getObject)для операцій з S3. - Внутрішнє легасі: огортаєш один раз на межі модуля, внутрішня логіка залишається без змін.
Питання на співбесіді
Q: Яка різниця між util.promisify і ручним загортанням через new Promise?
A: util.promisify автоматично розпізнає патерн (err, value) і враховує util.promisify.custom. Ручне new Promise дає повний контроль, але колбек-логіку пишеш сам. Для стандартних Node.js API util.promisify коротший і менш схильний до помилок.
Q: Що відбувається, якщо оригінальна функція передає кілька значень у колбек?
A: util.promisify резолвить у масив усіх аргументів після err. Наприклад, dns.lookup дає [address, family]. Деструктуруй, щоб отримати кожне значення окремо.
Q: Чи можна зробити promisify синхронної функції?
A: Технічно так, але сенсу немає. Promise резолвиться миттєво, а ти додаєш зайві накладні витрати.
Q: Чому не варто використовувати util.promisify зі стрімами?
A: Стріми потребують обробки зворотного тиску (backpressure). Promisify резолвить один раз і виходить, ігноруючи потік даних. Це може призвести до витоку пам'яті. Використовуй stream.pipeline або async-ітератори.
Приклади
Callback hell проти promisification
Читання двох файлів послідовно через колбеки перетворюється на піраміду. Promisification тримає код плоским.
const fs = require('fs');
const util = require('util');
// Колбек-версія - вкладеність зростає з кожним послідовним викликом
fs.readFile('data.txt', 'utf8', (err, data1) => {
if (err) return console.error(err);
fs.readFile('data2.txt', 'utf8', (err, data2) => {
if (err) return console.error(err);
console.log(data1 + data2); // Output: вміст обох файлів
});
});
// Promisified - той самий результат, читається зверху вниз
const readFile = util.promisify(fs.readFile);
try {
const data1 = await readFile('data.txt', 'utf8');
const data2 = await readFile('data2.txt', 'utf8');
console.log(data1 + data2); // Output: вміст обох файлів
} catch (err) {
console.error(err);
}Логіка і результат однакові. Кожен послідовний крок - один рядок, а не один рівень вкладеності.
Express-роут з promisified читанням файлів
Реальний патерн: читання конфігу користувача з диску перед відповіддю API.
const fs = require('fs');
const util = require('util');
const readFile = util.promisify(fs.readFile);
app.get('/user/:id', async (req, res) => {
try {
const userData = await readFile(`users/${req.params.id}.json`, 'utf8');
const settings = await readFile('app-settings.json', 'utf8');
res.json({
user: JSON.parse(userData), // Output: об'єкт користувача
settings: JSON.parse(settings) // Output: об'єкт налаштувань
});
} catch (err) {
res.status(500).json({ error: err.message }); // Output: { error: 'ENOENT...' }
}
});Один try/catch покриває обидва читання. Без promisification це два вкладених колбеки з окремими гілками помилок у кожному.
Коротка відповідь
Для співбесідиКоротка відповідь допоможе вам впевнено відповідати на цю тему під час співбесіди.