Привет!
Подумал я тут рассказать вам о том, как в JavaScript с помощью библиотеки Fluture можно создавать и использовать ленивые функции. Это будет краткий обзор на то, как создавать функции, как обрабатывать ошибки и чуть-чуть про параллелизм. Функциональным программированием мозги парить не буду! Обещаю!
Fluture
Fluture — библиотека, разработанная разработчиком Aldwin Vlasblom, реализующая Future. Future — альтернатива Promise, имеющая куда более мощный API, позволяющий реализовать отмену выполнения (cancellation), безопасную "рекурсию", "безошибочное" выполнение (используя Either) и ещё маленькую тележку крутых возможностей.
Думаю, стоит рассказать вам про методы (монады), которые я буду использовать в примерах ниже
.of(Any)— создает Future из переданного значения.map(Function)— нет, это неArray.map, это функция трансформации, аналогичнаяPromise.then.chainRej(Function)— анал��гичноPromise.catchловит ошибку.fork(Function, Function)— запускает выполнение Future
Создание ленивой функции
Для себя я выделил два основных подхода к созданию ленивых функций в Fluture. Первый подход заключается в том, что мы создаем функцию, которая принимает исходные данные и возвращает готовую к выполнению Future. Второй подход заключается в том, что мы создаем Future со всеми описанными трансформациями, а затем передаем ей данные.
Непонятно? Давайте на примере! Есть у нас вот такая функция
const multiply10 = x => x * 10;
Теперь сделаем её ленивой, используя первый подход
const multiply10 = x => x * 10; const lazyMultiply10 = (x) => Future .of(x) // Создаем Future из значения .map(multiply10); // Теперь наша функция тут lazyMultiply10(2).fork(console.error, console.log); // -> 20
Слишком громоздко, не правда ли? Попробуем записать более лаконично, используя второй подход.
const multiply10 = x => x * 10; const lazyMultiply10 = Future.map(multiply10); const value = Future.of(2); // Оборачиваем наше значение в Future lazyMultiply10(value).fork(console.error, console.log); // -> 20
Уже лучше, но все еще громоздко. Надо компактнее!
const lazyMultiply10 = Future.map(x => x * 10); lazyMultiply10(Future.of(2)).fork(console.error, console.log); // -> 20
На самом деле, эти подходы не являются взаимоисключающими и могут быть использоваться вместе.
const lazyMultiply10 = Future.map(x => x * 10); const someCalculation = a => Future .of(a) .map(v => v + 1) .chain(v => lazyMultiply10(Future.of(v)); someCalculation(10).fork(console.error, console.log); // -> 110
Обработка ошибок
Обработка ошибок в Future практически не отличается от обработки ошибов в Promise. Давайте вспомним представим функцию, которая делает запрос к стороннему, не очень стабильному, API.
const requestToUnstableAPI = query => request({ method: 'get', uri: `http://unstable-site.com/?${query}` }) .then(res => res.data.value) .catch(errorHandler);
Та же функция, но обернутая в Future
const lazyRequestToUnstableAPI = query => Future .tryP(() => request({ method: 'get', uri: `http://unstable-site.com/?${query}` })) .map(v => v.data.value) .chainRej(err => Future.of(errorHandler(err));
На самом деле, обработку ошибок можно сделать более гибкой. Для этого нам понадобится структура Either, а это малость выходит за рамки моего обзора.
Параллелизм
Для работы с параллелизмом в Future реализованы два метода race(Futures[]) (аналогичен Promise.race), parallel(n, Futures[]) и both(Future, Future), но он является частным случаем parallel.
Метод parallel принимает два аргумента, количество параллельно выполняемых Future и массив с Future. Чтобы сделать поведение parallel таким же как метод Promise.all, нужно количество выполняемых установить как Infinity.
Тут тоже без примеров не обойдемся
const requestF = o => Future.tryP(() => request(o)); const parallel1 = Future.parallel(1); const lazyReqs = parallel1( [ 'http://site.com', 'http://another-site.com', 'http://one-more-site.com', ] .map(requestF) ); lazyReqs.fork(console.error, console.log); // -> [Result1, Result2, Result3]
Совместимость с Promise
В JavaScript от Promise никуда не деться, да и вряд ли кто-то будет рад, если ваш метод будет возвращать какую-то непонятную Future. Для этого у Future есть метод .promise(), который, запустит выполнение Future и обернет её в Promise.
Future .of(10) .promise(); // -> Promise{value=10}
Ссылки
- Репозиторий библиотеки Fluture
- Статья на Medium от Aldwin Vlasblom "Broken Promises"
- Спецификация fantasy-land
Вот, пожалуй, и все, что я вам хотел рассказать. Если тема интересна, дайте знать, расскажу подробнее про обработку ошибок. И да, сильно меня не ругайте, это мой первый пост на Хабре.
