Объясните, как можно написать (или напишите) адаптеры асинхронности promisify и callbackify?nodejs-21

1. Promisify - преобразование callback-функций в Promise

Базовая реализация promisify

function promisify(original) {
  return function(...args) {
    return new Promise((resolve, reject) => {
      original.call(this, ...args, (err, result) => {
        if (err) {
          reject(err);
        } else {
          resolve(result);
        }
      });
    });
  };
}

Пример использования

const fs = require('fs');
const readFilePromise = promisify(fs.readFile);

readFilePromise('file.txt', 'utf8')
  .then(data => console.log(data))
  .catch(err => console.error(err));

Улучшенная версия с поддержкой:

  1. Методов объектов
  2. Нескольких результатов
  3. Пользовательского контекста
function advancedPromisify(original) {
  return function(...args) {
    return new Promise((resolve, reject) => {
      original.call(this, ...args, (err, ...results) => {
        if (err) {
          reject(err);
        } else {
          resolve(results.length > 1 ? results : results[0]);
        }
      });
    });
  };
}

2. Callbackify - преобразование Promise в callback-стиль

Базовая реализация callbackify

function callbackify(promiseFn) {
  return function(...args) {
    const callback = args.pop();
    if (typeof callback !== 'function') {
      throw new TypeError('Callback must be a function');
    }

    promiseFn.apply(this, args)
      .then(result => callback(null, result))
      .catch(err => callback(err));
  };
}

Пример использования

async function asyncFetchData(url) {
  // Реализация с async/await
}

const callbackFetchData = callbackify(asyncFetchData);

callbackFetchData('https://api.example.com', (err, data) => {
  if (err) return console.error(err);
  console.log(data);
});

Улучшенная версия с поддержкой:

  1. Прямых значений (не только Promise)
  2. Прогресса выполнения
  3. Отмены операций
function advancedCallbackify(promiseFn) {
  return function(...args) {
    const callback = args.pop();
    if (typeof callback !== 'function') {
      return promiseFn.apply(this, args);
    }

    try {
      const result = promiseFn.apply(this, args);
      if (result && typeof result.then === 'function') {
        result.then(
          val => callback(null, val),
          err => callback(err)
      } else {
        callback(null, result);
      }
    } catch (err) {
      callback(err);
    }
  };
}

Сравнение с нативными реализациями Node.js

Node.js уже включает эти утилиты в модуле util:

util.promisify

const { promisify } = require('util');
const sleep = promisify(setTimeout);

await sleep(1000); // Ждет 1 секунду

util.callbackify

const { callbackify } = require('util');
const callbackAsyncFn = callbackify(asyncFn);

Особенности реализации

  1. Обработка контекста:

    • Важно сохранять this при вызове оригинальной функции
    • Используем .call(this, ...) или .apply(this, ...)
  2. Множественные аргументы:

    • Callback-функции часто возвращают несколько значений
    • Promisify должен уметь их обрабатывать
  3. Ошибки:

    • Всегда проверяем первый аргумент в callback
    • В callbackify передаем ошибку первым аргументом
  4. Производительность:

    • Создание Promise - дорогая операция
    • Для горячих путей лучше избегать лишних преобразований

Резюмируем

  1. promisify:

    • Преобразует callback → Promise
    • Обрабатывает ошибки через reject
    • Поддерживает контекст и multiple results
  2. callbackify:

    • Преобразует Promise → callback
    • Передает ошибку первым аргументом
    • Работает с async/await функциями
  3. Лучшие практики:

    • Используйте нативные реализации из util
    • Для сложных случаев пишите кастомные адаптеры
    • Тестируйте обработку ошибок и контекст

Эти адаптеры позволяют плавно мигрировать между разными стилями асинхронного кода и интегрировать старые API с современными async/await подходами.