Future

Future — ленивая (без состояния) асинхронная абстракция: описание того, как получить значение, а не уже-запущенная операция. В отличие от Promise, Future не запускается до первого вызова .fork.

Что это / Зачем

Promise — eager: выполняется сразу при создании. Иногда нужно описать асинхронное вычисление декларативно и запустить позже (или несколько раз). Future:

  • ленивый — без состояния, без кеша результата;
  • является монадой (of + chain = unit + bind);
  • описывает цепочку вычислений, которая исполняется при fork(onSuccess, onError);
  • может выполняться многократно без побочных эффектов.

Идея пришла из FP (Haskell, Fantasy Land в JS, библиотека Fluture).

API / Синтаксис

// Минимальный Future: хранит executor, но не запускает его
class Future {
  constructor(executor) { this.executor = executor; }
  static of(value) { return new Future((s, _) => s(value)); }
  fork(onSuccess, onError) { this.executor(onSuccess, onError); }
  map(fn) {
    return new Future((s, e) =>
      this.fork((v) => s(fn(v)), e));
  }
  chain(fn) { // flatMap
    return new Future((s, e) =>
      this.fork((v) => fn(v).fork(s, e), e));
  }
}

// Использование
const f = Future
  .of(1)
  .map((x) => x + 1)
  .chain((x) => new Future((s) => setTimeout( => s(x * 10), 100)));

f.fork(console.log, console.error); // запуск!
// один и тот же future можно форкнуть несколько раз
f.fork(console.log, console.error);

Ключевые моменты

  • Без fork ничего не происходит — это главное отличие от Promise.
  • Future — чистая абстракция, нет внутреннего состояния → нет кеша, нет однократного разрешения.
  • Цепочка строится «вперёд», а исполнение разворачивается «назад» — fork идёт по цепочке к началу и стартует executor самого первого Future.
  • map(fn) — для трансформаций; chain(fn) — когда fn сама возвращает Future.
  • Контракт монады: Future.of (unit) + chain (bind) + ассоциативность.
  • В Promise/A+ thenable, в FP мире — Future/Task.

Подводные камни

  • Future вычисляется столько раз, сколько раз вызвали fork — если внутри HTTP, файл прочитается дважды.
  • Нет встроенной отмены — нужно делать самому через onCancel-колбэк.
  • Преобразование Future → Promise теряет ленивость.

🎓 Источники

  • 🎓 [Future. Asynchrony on Stateless Futures] · 2019-05-14 · YouTube
    • Тезисы:
      • Future ленивый и без состояния, в отличие от eager-Promise.
      • Контракт монады: of + chain.
      • Цепочка строится вперёд, исполнение разворачивается назад.
      • Без fork файл не читается. Несколько fork — несколько чтений.
      • Future ↔ Promise через адаптеры futureify / promisify.
    • Цитата:

      «Future ленивый и без состояния. Promise eager и со состоянием. Это две разные модели одной и той же сущности — отложенного значения.»

См. также