Запропонувати правкуПокращити цю статтюДопрацюйте відповідь до «Що таке promisification?». Ваші зміни проходять модерацію перед публікацією.Потрібне підтвердженняКонтентЩо ви змінюєте🇺🇸EN🇺🇦UAПереглядЗаголовок (UA)Коротка відповідь (UA)**Promisification** - це перетворення функції з колбек-стилем на функцію, яка повертає Promise. Дозволяє використовувати `async/await` замість вкладених колбеків. ```javascript const readFile = util.promisify(fs.readFile); const data = await readFile('file.txt', 'utf8'); ``` **Ключове:** `util.promisify` автоматично розпізнає стандартний Node.js патерн `(err, value)`.Показується над повною відповіддю для швидкого нагадування.Відповідь (UA)Зображення**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)`. ### Швидкий приклад ```javascript 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 стилю.** ```javascript const sleep = util.promisify(setTimeout); // TypeError: callback is not a function ``` `setTimeout` приймає простий колбек, не `(err, value)`. Замість цього: `new Promise(resolve => setTimeout(resolve, 1000))`. **Помилка: втрата `this` при promisify методів класу.** ```javascript 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))`. Прив'язуй екземпляр перед огортанням. Ця помилка частіше призводить до непомітних збоїв у продакшені, ніж неправильна сигнатура колбека. **Помилка: очікування одного значення, коли оригінал передає кілька.** ```javascript 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 тримає код плоским. ```javascript 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. ```javascript 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 це два вкладених колбеки з окремими гілками помилок у кожному.Для рев’юераПримітка для модератора (необов’язково)Бачить лише модератор. Прискорює рев’ю.