Fluture
Fluture предлагает структуру управления, аналогичную Promises, Tasks, Deferreds и тому подобным. Давайте называть их Futures.
Подобно Promises, Futures представляют значение, возникающее в результате успешного или неудачного выполнения асинхронной операции (ввода-вывода). В отличие от Promises, Futures являются ленивыми и соответствуют .
Некоторые из функций, предоставляемые Fluture, включают:
- Отмена.
- Утилиты управления ресурсами.
- Безопасная для стека композиция и рекурсия.
- Интеграция с Sanctuary.
- Приятный опыт отладки.
Для получения дополнительной информации:
- Документация API
- Статья: Введение в Fluture — функциональная альтернатива Promises
- Вики: Сравнение Futures и Promises
- Вики: Сравнение Fluture с похожими библиотеками
- Видео: Monad a Day — Futures от @DrBoolean
Установка
С помощью NPM
$ npm install --save fluture
Сборка с CDN
Чтобы загрузить Fluture непосредственно в браузер, на CodePen или , используйте один из следующих загружаемых файлов с сети доставки содержимого JSDelivr. Это отдельные файлы, в которых все зависимости Fluture предварительно скомпонованы.
-
Файл Fluture Script: Файл JavaScript, который добавляет
Flutureв глобальную область видимости. Идеально подходит для старых браузеров и CodePen. - Сжатый Fluture Script: То же самое, но сжатый.
- Модуль Fluture: Модуль EcmaScript с именованными экспортами. Идеально подходит для Deno или современных браузеров.
- Сжатый модуль Fluture: Сжатый модуль EcmaScript без типизации TypeScript. Не рекомендуется для Deno.
Использование
Модуль EcmaScript
Fluture написан как модульный JavaScript.
- На Node 14 и выше Fluture можно загрузить напрямую с помощью
import 'fluture'. - На Node 13 и ниже Fluture можно загрузить напрямую с помощью
import 'fluture/index.js'. - На Node 12 необходимо указать флаг
--experimental-modules. - На версиях Node ниже 12 можно использовать загрузчик esm. В качестве альтернативы доступен модуль CommonJS.
- Современные браузеры могут запускать Fluture напрямую. Если вы хотите это попробовать, я рекомендую установить Fluture с помощью Pika или Snowpack. Также можно попробовать скомпонованный модуль, чтобы избежать менеджера пакетов.
- Для старых браузеров используйте сборщик, такой как Rollup или WebPack. Помимо системы модулей, Fluture использует чисто ES5-совместимый синтаксис, поэтому исходный код не нужно транспилировать после сборки. В качестве альтернативы доступен модуль CommonJS.
import {readFile} from 'fs'
import {node, encase, chain, map, fork} from 'fluture'
const getPackageName = file => (
node (done => { readFile (file, 'utf8', done) })
.pipe (chain (encase (JSON.parse)))
.pipe (map (x => x.name))
)
getPackageName ('package.json')
.pipe (fork (console.error) (console.log)) Модуль CommonJS
Хотя исходный код Fluture использует систему модулей EcmaScript, файл main указывает на версию Fluture CommonJS.
В старых средах могут потребоваться полифиллы для одной или нескольких из следующих функций: , и .
const fs = require ('fs')
const Future = require ('fluture')
const getPackageName = function (file) {
return Future.node (function (done) { fs.readFile (file, 'utf8', done) })
.pipe (Future.chain (Future.encase (JSON.parse)))
.pipe (Future.map (function (x) { return x.name }))
}
getPackageName ('package.json')
.pipe (Future.fork (console.error) (console.log)) Документация
Содержание
Общие сведения
- Инструкции по установке
- Инструкции по использованию
- О проекте Fluture
- О взаимодействии с другими библиотеками
- Как читать сигнатуры типов
- Как работает отмена
- О безопасности стека
- Отладка с Fluture
- Преобразование Futures в строку
- Использование с Sanctuary
- Использование нескольких версий Fluture вместе
Создание новых Futures
Future: Создать потенциально отменяемый Futureresolve: Создать разрешенный Futurereject: Создать отклоненный Futureafter: Создать Future, который разрешается после таймаутаrejectAfter: Создать Future, который отклоняется после таймаутаgo: Создать "корутину" с помощью функции-генератораattempt: Создать Future с помощью потенциально бросающей функциейattemptP: Создать Future с помощью функции, возвращающей Promisenode: Создать Future с помощью обратного вызова в стиле Nodeencase: Преобразовать потенциально бросающую функцию в функцию FutureencaseP: Преобразовать функцию, возвращающую Promise, в функцию Future
Преобразование между API Nodeback и Futures
Преобразование между Promises и Futures
Преобразование и объединение Futures
pipe: Применить функцию к Future в цепочке методовmap: Синхронно обработать успешное значение в Futurebimap: Синхронно обработать успешное или неудачное значение в Futurechain: Асинхронно обработать успешное значение в Futurebichain: Асинхронно обработать успешное или неудачное значение в Futureswap: Поменять успешное значение с неудачнымmapRej: Синхронно обработать неудачное значение в FuturechainRej: Асинхронно обработать неудачное значение в Futurecoalesce: Преобразовать успешные и неудачные значения в одно успешное значениеap: Объединить успешные значения нескольких Futures с помощью функцииpap: Объединить успешные значения нескольких Futures параллельно с помощью функцииand: Логическое и для Futuresalt: Логическое или для Futureslastly: Выполнить Future после завершения предыдущегоrace: Гонка двух Futuresboth: Дождаться успешных значений двух Futuresparallel: Дождаться успешных значений всех Futures
Обработка/разветвление Futures
Утилиты и структуры данных, связанные с параллелизмом
pap: Объединить успешные значения нескольких Futures параллельно с помощью функцииrace: Гонка двух Futuresboth: Дождаться успешных значений двух Futuresparallel: Дождаться успешных значений всех FuturesConcurrentFuture: Отдельная структура данных для алгебраического параллелизмаalt: Ведёт себя какraceдляConcurrentFuture
Управление ресурсами
Другие утилиты
pipe: Применить функцию к Future в цепочке методовcache: Кэшировать Future, чтобы его можно было разветвить несколько разisFuture: Определить, является ли значение совместимым с Future Fluturenever: Future, который никогда не завершаетсяdebugMode: Настроить режим отладки Fluturecontext: Контекст отладки экземпляра Future
Бабочка
Название «Fluture» — это сочетание «FL» (аббревиатуры от ) и «future» (будущее). Fluture означает бабочка по-румынски: существо, которое можно ожидать увидеть в Стране чудес.
За стилизацию логотипа спасибо Эрику Фюэнте, а за спонсорство проекта — .
Взаимодействие
-
Futureреализует совместимый с Fantasy Land 1.0+Alt,Bifunctor,Monad, иChainRec(of,ap,alt,map,bimap,chain,chainRec). -
Future.Parреализует совместимый с Fantasy Land 3Alternative(of,zero,map,ap,alt). - Представители Future и ConcurrentFuture содержат свойства
@@typeдля Sanctuary Type Identifiers. - Экземпляры Future и ConcurrentFuture содержат свойства
@@showдля Sanctuary Show.
Типовые подписи
Различные подписи функций представлены на небольшом языке, известном как нотация Хиндли-Миллера.
Вкратце, синтаксис следующий: InputType -> OutputType. Теперь, поскольку функции в Fluture являются , «выход» функции часто представляет собой другую функцию. В нотации Хиндли-Миллера это записывается как InputputType -> InputToSecondFunction -> OutputType и так далее.
По соглашению, типы, начинающиеся с заглавной буквы, являются . Когда они начинаются со строчной буквы, это переменные типа. Вы можете рассматривать эти переменные типа как обобщенные типы. Так, a -> b обозначает функцию от обобщенного типа a к обобщенному типу b.
Наконец, с помощью так называемых , переменные типа могут быть вынуждены соответствовать «интерфейсу» (или классу типа функциональным языком). Например, MyInterface a => a -> b, обозначает функцию от обобщенного типа a к обобщенному типу b, при условии, что a должен реализовывать MyInterface.
Вы можете подробно ознакомиться с здесь.
Типы
Конкретные типы, с которыми вы столкнетесь в этом документе:
- Future - Экземпляры Future, предоставляемые совместимыми версиями Fluture.
-
ConcurrentFuture - Futures, обернутые с помощью (
Future.Par). -
Promise a b - Значения, которые соответствуют спецификации Promises/A+ и имеют причину отклонения типа
aи значение разрешения типаb. -
Nodeback a b - Обратный вызов в стиле Node; функция с сигнатурой
(a | Nil, b) -> x. -
Pair a b - Массив ровно с двумя элементами:
[a, b]. -
Iterator - Объекты с методами
next, соответствующими протоколу Iterator. - Cancel - Нульарные функции отмены, возвращаемые из вычислений.
-
Throwing e a b - Функция от
aкb, которая может бросить исключениеe. -
List - Внутренняя структура связанного списка Fluture:
{ head :: Any, tail :: List }. -
Context - Внутренний объект контекста отладки Fluture:
{ tag :: String, name :: String, stack :: String }.
Классы типов
Некоторые подписи содержат . Как правило, эти ограничения указывают на то, что какое-то значение должно соответствовать интерфейсу, определённому в .
- Functor - Значения, соответствующие Fantasy Land Functor.
- Bifunctor - Значения, соответствующие Fantasy Land Bifunctor.
- Chain - Значения, соответствующие Fantasy Land Chain.
- Apply - Значения, соответствующие Fantasy Land Apply.
- Alt - Значения, соответствующие Fantasy Land Alt.
Отмена
Отмена — это система, позволяющая работающим Future получить возможность остановить свои действия и освободить ресурсы, которые они удерживали, когда потребитель сообщает, что больше не заинтересован в результате.
Для отмены Future необходимо отписаться от него. Большинство возвращают функцию unsubscribe. Вызов её сигнализирует о том, что мы больше не заинтересованы в результате. После вызова unsubscribe, Fluture гарантирует, что наши обратные вызовы не будут вызваны; но что более важно: сигнал отмены отправляется вверх по потоку.
Сигнал отмены проходит до источника (за исключением кэшированных Future — см. ), позволяя всем участникам по пути очистить данные.
С помощью , мы можем предоставить обработчик отмены, вернув его из вычисления. Давайте посмотрим, как это выглядит:
// We use the Future constructor to create a Future instance.
const eventualAnswer = Future (function computeTheAnswer (rej, res) {
// We give the computer time to think about the answer, which is 42.
const timeoutId = setTimeout (res, 60000, 42)
// Here is how we handle cancellation. This signal is received when nobody
// is interested in the answer any more.
return function onCancel () {
// Clearing the timeout releases the resources we were holding.
clearTimeout (timeoutId)
}
})
// Now, let's fork our computation and wait for an answer. Forking gives us
// the unsubscribe function.
const unsubscribe = fork (log ('rejection')) (log ('resolution')) (eventualAnswer)
// After some time passes, we might not care about the answer any more.
// Calling unsubscribe will send a cancellation signal back to the source,
// and trigger the onCancel function.
unsubscribe () Многие естественные источники в Fluture имеют собственные обработчики отмены. , например, делает ровно то, что мы только что сделали: вызывая clearTimeout.
Наконец, Fluture отписывается от Future, которые он форчит за нас, когда ему больше не нужен результат. Например, оба Future, переданные в , форчатся, но как только один из них производит результат, другой отписывается, вызывая отмену. Это означает, что в целом отписка и отмена полностью управляются за кулисами.
Безопасность стека
Fluture интерпретирует наши преобразования безопасным образом для стека. Это означает, что ни одна из следующих операций не приводит к RangeError: Maximum call stack size exceeded:
> const add1 = x => x + 1
> let m = resolve (1)
> for (let i = 0; i < 100000; i++) {
. m = map (add1) (m)
. }
> fork (log ('rejection')) (log ('resolution')) (m)
[resolution]: 100001 > const m = (function recur (x) {
. const mx = resolve (x + 1)
. return x < 100000 ? chain (recur) (mx) : mx
. }(1))
> fork (log ('rejection')) (log ('resolution')) (m)
[resolution]: 100001 Чтобы узнать больше о памяти и использовании стека при различных типах рекурсии, см. (или выполните).
Отладка
Прежде всего, Fluture проверяет все входные данные и выбрасывает TypeErrors, когда предоставляется неправильный ввод. Сообщения, которые они содержат, разработаны, чтобы предоставить достаточно информации, чтобы понять, что пошло не так.
Во-вторых, Fluture перехватывает исключения, которые выбрасываются асинхронно, и предоставляет их вам одним из двух способов:
- Выбрасывая Error при его возникновении.
- Вызывая ваш обработчик исключений с Error.
Исходное исключение не используется, так как оно могло иметь любое значение. Вместо этого создается обычный экземпляр JavaScript Error, свойства которого основаны на исходном исключении. Его свойства следующие:
-
name: Всегда просто"Error". -
message: Исходное сообщение об ошибке или сообщение, описывающее значение. -
reason: Исходное значение, перехваченное Fluture. -
context: Связанный список объектов «контекста». Это используется для создания свойстваstack, и вам обычно не нужно смотреть на него. Если режим отладки не включен, список всегда пуст. -
stack: Трассировка стека исходного исключения, если она была, или собственная трассировка стека Error в противном случае. Если режим отладки (см. ниже) включен, включаются дополнительные трассировки стеков из шагов, предшествующих сбою. -
future: ЭкземплярFuture, который потреблялся потреблялся при возникновении исключения. Часто печать его как строки может дать полезную информацию. Вы также можете попытаться потреблять его изолированно, чтобы лучше определить, что идёт не так.
Наконец, как упоминалось, Fluture имеет , в котором собирается дополнительная контекстная информация на нескольких тактах JavaScript, включается в виде расширенной «трассировки асинхронного стека» в ошибках и .
Режим отладки может значительно повлиять на производительность и использовать память, поэтому я не рекомендую его использовать в рабочей среде.
Преобразование Future в строку
Есть несколько способов вывести Future в строку. Возьмём простой пример вычисления:
const add = a => b => a + b; const eventualAnswer = ap (resolve (22)) (map (add) (resolve (20)));
-
Преобразование в строку напрямую, вызвав
String(eventualAnswer)илиeventualAnswer.toString(), даст приближение кода, используемого для создания Future. В этом случае:"ap (resolve (22)) (map (a => b => a + b) (resolve (20)))"
-
Преобразование в строку с помощью
JSON.stringify(eventualAnswer, null, 2)даст своего рода абстрактное синтаксическое дерево.{ "$": "fluture/Future@5", "kind": "interpreter", "type": "transform", "args": [ { "$": "fluture/Future@5", "kind": "interpreter", "type": "resolve", "args": [ 20 ] }, [ { "$": "fluture/Future@5", "kind": "transformation", "type": "ap", "args": [ { "$": "fluture/Future@5", "kind": "interpreter", "type": "resolve", "args": [ 22 ] } ] }, { "$": "fluture/Future@5", "kind": "transformation", "type": "map", "args": [ null ] } ] ] }
Sanctuary
При использовании этого модуля с (и вследствие этого) можно столкнуться со следующей проблемой:
> import S from 'sanctuary'
> import {resolve} from 'fluture'
> S.I (resolve (1))
! TypeError: Since there is no type of which all the above values are members,
. the type-variable constraint has been violated. Это происходит потому, что Sanctuary Def должен знать о типах, созданных Fluture, чтобы определить, являются ли переменные типа согласованными.
Чтобы сообщить Sanctuary об этих типах, мы можем получить определения типов из и передать их в :
> import sanctuary from 'sanctuary'
> import {env as flutureEnv} from 'fluture-sanctuary-types'
> import {resolve} from 'fluture'
> const S = sanctuary.create ({checkTypes: true, env: sanctuary.env.concat (flutureEnv)})
> fork (log ('rejection'))
. (log ('resolution'))
. (S.I (resolve (42)))
[resolution]: 42 Несовместимые версии Fluture
Большинство версий Fluture понимают, как потреблять экземпляры из большинства других версий, даже через основные релизы Fluture. Это позволяет различным пакетам, зависящим от Fluture, взаимодействовать.
Однако иногда неизбежно, что выпущена более новая версия Fluture, которая больше не может понимать более старые версии, и наоборот. Это происходит только в случае основных релизов и будет указано в журнале изменений. Когда две несовместимые версии Fluture сталкиваются с экземплярами, они делают всё возможное, чтобы выдать ясное сообщение об этом.
Когда это происходит, вам необходимо вручную преобразовать более старый экземпляр в более новый экземпляр Future. Когда возвращает false, требуется преобразование. Вы также можете применить этот трюк, если Future поступает из другой библиотеки, похожей на Fluture.
const NoFuture = require ('incompatible-future')
const incompatible = NoFuture.of ('Hello')
const compatible = Future ((rej, res) => {
return NoFuture.fork (rej) (res) (incompatible)
})
both (compatible) (resolve ('world')) Создание Future
Future
Future :: ((a -> Undefined, b -> Undefined) -> Cancel) -> Future a b
Создаёт Future с данным вычислением. Вычисление — это функция, которая принимает два обратных вызова. Оба являются продолжениями вычисления. Первый — это reject, обычно сокращённый до rej; второй — это resolve, или res . Когда вычисление завершается (возможно, асинхронно), оно может вызвать соответствующее продолжение со значением ошибки или успеха.
Кроме того, вычисление должно вернуть нульарную функцию, содержащую логику отмены. См. .
Если вы обнаружите, что нет способа отменить своё вычисление, вы можете вернуть функцию noop в качестве функции отмены. Однако в этот момент, обычно, есть более подходящий способ (например, через ).
> fork (log ('rejection'))
. (log ('resolution'))
. (Future (function computation (reject, resolve) {
. const t = setTimeout (resolve, 20, 42)
. return () => clearTimeout (t)
. }))
[resolution]: 42 resolve
resolve :: b -> Future a b
Создаёт Future, который сразу же разрешается заданным значением.
> fork (log ('rejection'))
. (log ('resolution'))
. (resolve (42))
[answer]: 42 reject
reject :: a -> Future a b
Создаёт Future, который сразу же отклоняется заданным значением.
> fork (log ('rejection'))
. (log ('resolution'))
. (reject ('It broke!'))
[rejection]: "It broke!" after
after :: Number -> b -> Future a b
Создаёт Future, который разрешается заданным значением после заданного количества миллисекунд.
> fork (log ('rejection'))
. (log ('resolution'))
. (after (20) (42))
[resolution]: 42 rejectAfter
rejectAfter :: Number -> a -> Future a b
Создаёт Future, который отклоняется с заданным сообщением об ошибке после заданного количества миллисекунд.
> fork (log ('rejection'))
. (log ('resolution'))
. (rejectAfter (20) ('It broke!'))
[rejection]: "It broke!" go
go :: (() -> Iterator) -> Future a b
Способ выполнить async/await с Future, аналогично Promise Coroutines или Haskell Do-notation.
Принимает функцию, которая возвращает Future, обычно генератор-функцию, и цепляет каждое произведённое Future за предыдущее.
> fork (log ('rejection')) (log ('resolution')) (go (function*() {
. const thing = yield after (20) ('world')
. const message = yield after (20) ('Hello ' + thing)
. return message + '!'
. }))
[resolution]: "Hello world!" Отклоняющееся Future прерывает всю корутину.
> fork (log ('rejection')) (log ('resolution')) (go (function*() {
. const thing = yield reject ('It broke!')
. const message = yield after (20) ('Hello ' + thing)
. return message + '!'
. }))
[rejection]: "It broke!" Чтобы обработать отклонения внутри корутины, нам нужно поместить ошибку в нашу область управления.
Я рекомендую использовать coalesce с корректором.
> const control = coalesce (S.Left) (S.Right)
> fork (log ('rejection')) (log ('resolution')) (go (function*() {
. const thing = yield control (reject ('It broke!'))
. return S.either (x => `Oh no! ${x}`)
. (x => `Yippee! ${x}`)
. (thing)
. }))
[resolution]: "Oh no! It broke!" attempt
attempt :: Throwing e Undefined r -> Future e r
Создаёт Future, который разрешается результатом вызова заданной функции или отклоняется с ошибкой, выброшенной заданной функцией.
Сокращённая запись для .
> const data = {foo: 'bar'}
> fork (log ('rejection'))
. (log ('resolution'))
. (attempt (() => data.foo.bar.baz))
[rejection]: new TypeError ("Cannot read property 'baz' of undefined") attemptP
attemptP :: (Undefined -> Promise a b) -> Future a b
Создаёт Future, которое при разветвлении порождает Promise с использованием заданной функции и разрешается значением её разрешения или отклоняется с причиной её отклонения.
Сокращённая запись для .
> fork (log ('rejection'))
. (log ('resolution'))
. (attemptP (() => Promise.resolve (42)))
[resolution]: 42 node
node :: (Nodeback e r -> x) -> Future e r
Создаёт Future, которое отклоняется с первым аргументом, переданным в функцию, или разрешается со вторым, если первого нет.
Обратите внимание, что эта функция не поддерживает отмену.
> fork (log ('rejection'))
. (log ('resolution'))
. (node (done => done (null, 42)))
[resolution]: 42 encase
encase :: Throwing e a r -> a -> Future e r
Принимает функцию и значение, и возвращает Future, которое при разветвлении вызывает функцию со значением и разрешается результатом. Если функция выбрасывает исключение, оно перехватывается, и Future отклонится с исключением.
Применение encase с функцией f создаёт "безопасную" версию f. Вместо выбрасывания исключений, инкапсулированная версия всегда возвращает Future.
> fork (log ('rejection'))
. (log ('resolution'))
. (encase (JSON.parse) ('{"foo" = "bar"}'))
[rejection]: new SyntaxError ('Unexpected token =') encaseP
encaseP :: (a -> Promise e r) -> a -> Future e r
Преобразует функции, возвращающие Promise, в функции, возвращающие Future.
Принимает функцию, возвращающую Promise, и значение, и возвращает Future. При разветвлении Future вызывает функцию со значением для создания Promise и разрешается значением её разрешения или отклоняется с причиной её отклонения.
> encaseP (fetch) ('https://api.github.com/users/Avaq')
. .pipe (chain (encaseP (res => res.json ())))
. .pipe (map (user => user.name))
. .pipe (fork (log ('rejection')) (log ('resolution')))
[resolution]: "Aldwin Vlasblom" Преобразование Futures
map
map :: Functor m => (a -> b) -> m a -> m b
Преобразует значение разрешения внутри Future или , и возвращает Future или Functor с новым значением. Преобразование применяется только к ветви разрешения: если Future отклоняется, преобразование игнорируется.
См. также и .
> fork (log ('rejection'))
. (log ('resolution'))
. (map (x => x + 1) (resolve (41)))
[resolution]: 42 Для сравнения, приближение с Promise:
> Promise.resolve (41)
. .then (x => x + 1)
. .then (log ('resolution'), log ('rejection'))
[resolution]: 42 bimap
bimap :: Bifunctor m => (a -> c) -> (b -> d) -> m a b -> m c d
Применяет левую функцию к причине отклонения или правую функцию к значению разрешения в зависимости от того, какое из них присутствует. Может использоваться с любым .
> fork (log ('rejection'))
. (log ('resolution'))
. (bimap (x => x + '!') (x => x + 1) (resolve (41)))
[resolution]: 42
> fork (log ('rejection'))
. (log ('resolution'))
. (bimap (x => x + '!') (x => x + 1) (reject ('It broke!')))
[rejection]: "It broke!!" Для сравнения, приближение с Promise:
> Promise.resolve (41)
. .then (x => x + 1, x => Promise.reject (x + '!'))
. .then (log ('resolution'), log ('rejection'))
[resolution]: 42
> Promise.reject ('It broke!')
. .then (x => x + 1, x => Promise.reject (x + '!'))
. .then (log ('resolution'), log ('rejection'))
[rejection]: "It broke!!" chain
chain :: Chain m => (a -> m b) -> m a -> m b
Последовательно выполняет новое Future или с использованием значения разрешения из другого. Аналогично , chain ожидает функцию. Но вместо возвращения нового значения, chain ожидает возвращения Future (или экземпляра того же Chain).
Преобразование применяется только к ветви разрешения: если Future отклоняется, преобразование игнорируется.
См. также .
> fork (log ('rejection'))
. (log ('resolution'))
. (chain (x => resolve (x + 1)) (resolve (41)))
[resolution]: 42 Для сравнения, приближение с Promise:
> Promise.resolve (41)
. .then (x => Promise.resolve (x + 1))
. .then (log ('resolution'), log ('rejection'))
[resolution]: 42 bichain
bichain :: (a -> Future c d) -> (b -> Future c d) -> Future a b -> Future c d
Последовательно выполняет новое Future, используя либо значение разрешения, либо значение отклонения из другого. Аналогично , bichain ожидает две функции. Но вместо возвращения нового значения, bichain ожидает возвращения Futures.
> fork (log ('rejection'))
. (log ('resolution'))
. (bichain (resolve) (x => resolve (x + 1)) (resolve (41)))
[resolution]: 42
> fork (log ('rejection'))
. (log ('resolution'))
. (bichain (x => resolve (x + 1)) (resolve) (reject (41)))
[resolution]: 42 Для сравнения, приближение с Promise:
> Promise.resolve (41)
. .then (x => Promise.resolve (x + 1), Promise.resolve)
. .then (log ('resolution'), log ('rejection'))
[resolution]: 42
> Promise.reject (41)
. .then (Promise.resolve, x => Promise.resolve (x + 1))
. .then (log ('resolution'), log ('rejection'))
[resolution]: 42 swap
swap :: Future a b -> Future b a
Меняет местами ветви отклонения и разрешения.
> fork (log ('rejection'))
. (log ('resolution'))
. (swap (resolve (42)))
[rejection]: 42
> fork (log ('rejection'))
. (log ('resolution'))
. (swap (reject (42)))
[resolution]: 42 mapRej
mapRej :: (a -> c) -> Future a b -> Future c b
Преобразует причину отклонения Future. Это как , но для ветви отклонения.
> fork (log ('rejection'))
. (log ('resolution'))
. (mapRej (s => `Oh no! ${s}`) (reject ('It broke!')))
[rejection]: "Oh no! It broke!" Для сравнения, приближение с Promise:
> Promise.reject ('It broke!')
. .then (null, s => Promise.reject (`Oh no! ${s}`))
. .then (log ('resolution'), log ('rejection'))
[rejection]: "Oh no! It broke!" chainRej
chainRej :: (a -> Future c b) -> Future a b -> Future c b
Цепляет по причине отклонения Future. Это как , но для ветви отклонения.
> fork (log ('rejection'))
. (log ('resolution'))
. (chainRej (s => resolve (`${s} But it's all good.`)) (reject ('It broke!')))
[resolution]: "It broke! But it's all good." Для сравнения, приближение с Promise:
> Promise.reject ('It broke!')
. .then (null, s => `${s} But it's all good.`)
. .then (log ('resolution'), log ('rejection'))
[resolution]: "It broke! But it's all good." coalesce
coalesce :: (a -> c) -> (b -> c) -> Future a b -> Future d c
Применяет левую функцию к значению отклонения или правую функцию к значению разрешения в зависимости от того, какое из них присутствует, и разрешается результатом.
Это предоставляет удобный способ гарантировать, что Future всегда разрешается. Его можно использовать с другими конструкторами типов, такими как , для поддержания представления об ошибке.
> fork (log ('rejection'))
. (log ('resolution'))
. (coalesce (S.Left) (S.Right) (resolve ('hello'))
[resolution]: Right ("hello")
> fork (log ('rejection'))
. (log ('resolution'))
. (coalesce (S.Left) (S.Right) (reject ('It broke!'))
[resolution]: Left ("It broke!") Для сравнения, приближение с Promise:
> Promise.resolve ('hello')
. .then (S.Right, S.Left)
. .then (log ('resolution'), log ('rejection'))
[resolution]: Right ("hello")
> Promise.reject ('It broke!')
. .then (S.Right, S.Left)
. .then (log ('resolution'), log ('rejection'))
[resolution]: Left ("It broke!") Комбинирование Futures
ap
ap :: Apply m => m a -> m (a -> b) -> m b
Применяет функцию, содержащуюся в правом Future или , к значению, содержащемуся в левом Future или Apply. Этот процесс можно повторять, чтобы постепенно заполнять несколько аргументов функции с помощью функции с несколькими аргументами, как показано ниже.
Обратите внимание, что Futures будут выполняться последовательно — а не параллельно* — из-за монадной природы Futures. Порядок выполнения, как указано в Fantasy Land, m (a -> b) сначала, затем m a. Таким образом, справа налево.
* Посмотрите на для функции ap, которая выполняет свои аргументы параллельно. Если вам необходимо использовать ap (потому что вы создаёте обобщённую функцию), но вы всё равно хотите, чтобы Futures, передаваемые в неё, выполнялись параллельно, то вы можете использовать вместо.
> fork (log ('rejection'))
. (log ('resolution'))
. (ap (resolve (7)) (ap (resolve (49)) (resolve (x => y => x - y))))
[resolution]: 42 pap
pap :: Future a b -> Future a (b -> c) -> Future a c
Имеет тот же тип сигнатуры и функции, что и , но выполняет два заданных Future параллельно. Смотрите также для более общего способа достижения этого.
> fork (log ('rejection'))
. (log ('resolution'))
. (pap (resolve (7)) (pap (resolve (49)) (resolve (x => y => x - y))))
[resolution]: 42 alt
alt :: Alt f => f a -> f a -> f a
Выбор одного из двух .
Ведёт себя как логическое или для экземпляров , возвращая новое Future, которое либо разрешается первым значением разрешения, либо отклоняется последней причиной отклонения. Мы можем использовать его, если хотим, чтобы вычисление выполнялось только в том случае, если другое завершилось неудачно.
Обратите внимание, что Futures будут выполняться последовательно — а не параллельно* — из-за монадной природы Futures. Правое Future вычисляется перед левым Future.
См. также и .
* Если вы хотите использовать параллельную реализацию alt, вы можете просто использовать . Кроме того, вы можете обернуть ваши экземпляры Future с перед передачей их в alt.
> fork (log ('rejection'))
. (log ('resolution'))
. (alt (resolve ('left')) (resolve ('right')))
[resolution]: "right"
> fork (log ('rejection'))
. (log ('resolution'))
. (alt (resolve ('left')) (reject ('It broke!')))
[resolution]: "left" and
and :: Future a c -> Future a b -> Future a c
Логическое и для Futures.
Возвращает новое Future, которое либо отклоняется с первой причиной отклонения, либо разрешается последним значением разрешения, когда и только когда оба Future разрешаются. Мы можем использовать его, если хотим, чтобы вычисление выполнялось только после успешного завершения другого.
Левое Future вычисляется перед правым.
См. также и .
> fork (log ('rejection'))
. (log ('resolution'))
. (and (resolve ('left')) (resolve ('right')))
[resolution]: "left"
> fork (log ('rejection'))
. (log ('resolution'))
. (and (resolve ('left')) (reject ('It broke!')))
[rejection]: "It broke!" lastly
lastly :: Future a c -> Future a b -> Future a b
Выполняет второе Future после того, как первое завершится (успешно или неудачно). Отклоняется с причиной отклонения первого или второго Future или разрешается со значением разрешения первого Future. Это можно использовать для выполнения вычисления после того, как другое завершится, успешно или неудачно.
Если вы ищете способ очистить ресурсы после выполнения вычисления, которое их приобрело, вы должны использовать , который имеет намного больше проверок на случай ошибок.
См. также и .
> fork (log ('rejection'))
. (log ('resolution'))
. (lastly (encase (log ('lastly')) ('All done!')) (resolve (42)))
[lastly]: "All done!"
[resolution]: 42 Использование Futures
fork
fork :: (a -> Any) -> (b -> Any) -> Future a b -> Cancel
Выполните вычисление, представленное Future, передав reject и resolve обратные вызовы для продолжения после получения результата.
Эта функция называется fork потому, что она буквально представляет собой разветвление в нашей программе: точку, где единственный путь кода разделяется на два. По этой причине рекомендуется свести к минимуму количество вызовов fork. Чем больше разветвлений, тем выше сложность кода.
Как правило, вызывать fork нужно только в одном месте всей программы.
После запуска Future вычисление начнётся. Если программа решит в процессе, что ей больше не нужен результат вычисления, она может вызвать функцию unsubscribe, возвращаемую функцией fork. См. .
Если во время вычисления возникла ошибка, она будет повторно выброшена функцией fork и, скорее всего, не будет перехвачена. Вы можете обработать её с помощью process.on('uncaughtException') в Node или использовать .
Практически все примеры кода в Fluture используют fork для выполнения вычисления. Существуют некоторые вариации fork, которые служат различным целям ниже.
forkCatch
forkCatch :: (Error -> Any) -> (a -> Any) -> (b -> Any) -> Future a b -> Cancel
Улучшенная версия , которая позволяет нам реагировать на фатальную ошибку собственным способом. Фатальные ошибки возникают при возникновении непредвиденных исключений, при неправильном использовании API Fluture или при невозможности освобождения ресурсов.
Обработчик исключений всегда будет вызываться с экземпляром Error, независимо от причины сбоя.
Использование этой функции — это компромисс;
Как правило, лучше позволять программе аварийно завершаться и перезапускаться при возникновении фатальной ошибки. Перезапуск — это самый надёжный способ восстановить память, выделенную программой, до ожидаемого состояния.
Используя forkCatch, мы можем сохранить нашу программу активной после фатальной ошибки, что может быть очень полезно, когда программа используется несколькими клиентами. Однако, поскольку фатальные ошибки могут указывать на то, что где-то в программе возникло недопустимое состояние, вероятно, лучше всего перезапустить программу при обнаружении такой ошибки.
См. для получения информации об объекте Error, который передается обработчику исключений.
> forkCatch (log ('fatal error'))
. (log ('rejection'))
. (log ('resolution'))
. (map (x => x.foo) (resolve (null)))
[fatal error]: new Error ("Cannot read property 'foo' of null") значение
value :: (b -> Any) -> Future a b -> Cancel
Аналогично , но только для ветви разрешения. Используйте эту функцию только в том случае, если уверены, что Future будет разрешен, например, после использования . Если Future отклоняется, value выбросит ошибку.
Как и в случае с , value возвращает функцию unsubscribe. См. .
> value (log ('resolution')) (resolve (42))
[resolution]: 42 выполнено
done :: Nodeback a b -> Future a b -> Cancel
Запустите Future, используя в качестве продолжения.
Это аналогично , но вместо того, чтобы принимать две унарные функции, она принимает одну бинарную функцию.
Как и в случае с , value возвращает функцию unsubscribe. См. .
> done ((err, val) => log ('resolution') (val)) (resolve (42))
[resolution]: 42 обещание
promise :: Future Error a -> Promise Error a
Запустите Future и получите Promise для представления его продолжения.
Возвращает Promise, который разрешается значением разрешения или отклоняется причиной отклонения Future.
Если во время вычисления возникло исключение, обещание отклонится с ним. Рекомендуется использовать перед promise, чтобы гарантировать, что исключения и отклонения не смешиваются в ветви отклонения Promise.
Возможности отмены теряются при использовании promise для потребления Future.
> promise (resolve (42)) .then (log ('resolution'))
[resolution]: 42
> promise (reject ('failure')) .then (log ('resolution'), log ('rejection'))
[rejection]: "failure" Параллелизм
соревнование
race :: Future a b -> Future a b -> Future a b
Соревнование двух Future друг с другом. Создает новое Future, которое разрешается или отклоняется значением разрешения или отклонения первого Future, которое завершилось.
Когда одно Future завершается, другое автоматически отменяется.
> fork (log ('rejection'))
. (log ('resolution'))
. (race (after (15) ('left')) (after (30) ('right')))
[resolution]: "left" оба
both :: Future a b -> Future a c -> Future a (Pair b c)
Запускает два Future параллельно и получает результатов. Когда любое из Future отклоняется, другое Future отменяется, и результирующее Future отклоняется.
> fork (log ('rejection'))
. (log ('resolution'))
. (both (after (15) ('left')) (after (30) ('right')))
[resolution]: ["left", "right"] параллельно
parallel :: PositiveInteger -> Array (Future a b) -> Future a (Array b)
Создает Future, которое при запуске выполняет все Future в заданном массиве параллельно, гарантируя, что не более limit Future работают одновременно.
В следующем примере мы запускаем до 5 Future параллельно. Каждое Future занимает около 20 мс для завершения, что означает, что результат должен появиться через примерно 40 мс.
Если мы используем 1 для ограничения, Future будут выполняться последовательно, что приведет к появлению результата только через 200 мс.
Мы также можем использовать Infinity в качестве ограничения. Это создаст функцию, похожую на Promise.all, которая всегда запускает все Future параллельно. Однако это может легко привести к тому, что вычисление будет потреблять слишком много ресурсов, поэтому я бы посоветовал использовать число, примерно равное максимальному размеру массива, который, по вашему мнению, должна обрабатывать ваша программа.
> fork (log ('rejection'))
. (log ('resolution'))
. (parallel (5) (Array.from (Array (10) .keys ()) .map (after (20))))
[resolution]: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] Когда одно Future отклоняется, все текущие выполняемые Future отменяются, и результирующее Future отклоняется. Если вы хотите завершить все Future, даже если некоторые из них могут завершиться неудачно, вы можете использовать parallel в сочетании с .
> fork (log ('rejection'))
. (log ('resolution'))
. (parallel (2) ([resolve (42), reject ('It broke!')]
. .map (coalesce (S.Left) (S.Right))))
[resolution]: [Right (42), Left ("It broke!")] ConcurrentFuture
Тип ConcurrentFuture очень похож на тип Future, за исключением того, что у него семантика параллельности, в то время как у Future семантика последовательности.
Эта семантика наиболее заметна в реализации Applicative для ConcurrentFuture. При использовании над двумя ConcurrentFutures они выполняются параллельно, тогда как обычные экземпляры Future выполнялись бы последовательно. Это означает, что ConcurrentFuture не может быть монадой, поэтому мы имеем его как отдельный тип.
Реализация Alternative на ConcurrentFuture также имеет параллельную семантику. В то время как на обычных Future использует эффект ошибки для определения победителя, на ConcurrentFutures используется время, и победителем будет то ConcurrentFuture, которое завершилось первым.
Идея заключается в том, что мы можем переключаться между Future и ConcurrentFuture, используя и , чтобы получить последовательное или параллельное поведение соответственно. Это полезный тип для передачи абстракциям, которые не знают о функциях, специфичных для Future, таких как или , но знают, как работать с Apply и Alternative.
//Some dummy values
const x = 41;
const f = a => a + 1;
//The following two are equal ways to construct a ConcurrentFuture
const parx = S.of (Par) (x)
const parf = Par (S.of (Future) (f))
//We can make use of parallel apply
value (log ('resolution')) (seq (ap (parx) (parf)))
[resolution]: 42
//Concurrent sequencing
value (log ('resolution')) (seq (S.sequence (Par) ([parx, parx, parx])))
[resolution]: [41, 41, 41]
//And concurrent alt
value (log ('resolution')) (alt (after (15) ('left')) (after (30) ('right')))
[resolution]: "left" Par
Par :: Future a b -> ConcurrentFuture a b
Преобразует Future в ConcurrentFuture.
seq
Преобразует ConcurrentFuture в Future.
seq :: ConcurrentFuture a b -> Future a b
Управление ресурсами
Функции, перечисленные в этой категории, позволяют более точно управлять потоком полученных значений.
крючок
hook :: Future a b -> (b -> Future c d) -> (b -> Future a e) -> Future a e
Объединяет получение, потребление и освобождение ресурсов таким образом, чтобы вы могли быть уверены, что ресурс всегда будет освобожден, если он был получен, даже если во время потребления произойдет исключение; Иногда называется bracketing.
Подпись такая же, как у hook (acquire, dispose, consume), где:
-
acquire— это Future, которое может создавать подключения, открывать файлы и т. д. -
dispose— это функция, которая принимает результат изacquireи должна использоваться для очистки (закрытия подключений и т. д.). Возвращаемое ею Future должно разрешаться, а его значение разрешения игнорируется. Если оно отклоняется, генерируется фатальная ошибка, которая может быть обработана только с помощьюforkCatch. -
consume— это другая функция, которая принимает результат изacquire, и может использоваться для выполнения любых произвольных вычислений с использованием ресурса.
Обычно вы хотите частично применить эту функцию к первым двум аргументам (получение и освобождение), как показано в примере.
> import {open, read, close} from 'fs'
> const withFile = hook (node (done => open ('package.json', 'r', done)))
. (fd => node (done => close (fd, done)))
> fork (log ('rejection'))
. (log ('resolution'))
. (withFile (fd => node (done => (
. read (fd, Buffer.alloc (1), 0, 1, null, (e, _, x) => done (e, x)))
. )))
[resolution]: <Buffer 7b> Когда отменяется Future с крючком во время получения ресурса, ничего больше не происходит. Однако, если оно отменяется после завершения получения, освобождение все равно выполнится, и если оно завершится неудачно, будет выброшено исключение.
Если у вас есть несколько ресурсов, которые вы хотите потреблять все сразу, вы можете использовать для объединения нескольких крючков в один.
Функции для работы с данными
труба
Future.prototype.pipe :: Future a b ~> (Future a b -> c) -> c
Метод, доступный для всех Future, позволяющий включать произвольные функции над Future в цепочку методов в стиле fluent.
Вы можете рассматривать это как резерв для .
> resolve (x => y => x * y)
. .pipe (ap (after (20) (Math.PI)))
. .pipe (ap (after (20) (13.37)))
. .pipe (map (Math.round))
. .pipe (fork (log ('rejection')) (log ('resolution')))
[resolution]: 42 кэш
cache :: Future a b -> Future a b
Возвращает Future, которое кеширует значение разрешения или причину отклонения заданного Future, так что при каждом форке оно может загружать значение из кэша, а не повторно выполнять базовое вычисление.
Это по сути превращает одноадресное Future в многоадресное Future, позволяя нескольким потребителям подписаться на один и тот же результат. Базовое вычисление никогда не выполняется, пока все потребители не отменят подписку до его завершения.
Существует существенный недостаток использования cache, который заключается в том, что возвращаемые Future больше не являются референциально прозрачными, что усложняет рассуждения о них и рефакторинг кода, который их использует.
> import {readFile} from 'fs'
> const eventualPackageName = (
. node (done => readFile ('package.json', 'utf8', done))
. .pipe (chain (encase (JSON.parse)))
. .pipe (chain (encase (x => x.name)))
. .pipe (map (data => {
. log ('debug') ('Read, parsed, and traversed the package data')
. return data
. }))
. )
> fork (log ('rejection')) (log ('resolution')) (eventualPackageName)
[debug]: "Read, parsed, and traversed the package data"
[resolution]: "Fluture"
> fork (log ('rejection')) (log ('resolution')) (eventualPackageName)
[debug]: "Read, parsed, and traversed the package data"
[resolution]: "Fluture"
> const eventualCachedPackageName = cache (eventualPackageName)
> fork (log ('rejection')) (log ('resolution')) (eventualCachedPackageName)
[debug]: "Read, parsed, and traversed the package data"
[resolution]: "Fluture"
> fork (log ('rejection')) (log ('resolution')) (eventualCachedPackageName)
[resolution]: "Fluture" isFuture
isFuture :: a -> Boolean
Возвращает true для и false для всего остального. Эта функция (и ) также возвращает true для экземпляров Future, созданных в других контекстах. Поэтому рекомендуется использовать ее вместо instanceof, если только ваша цель не состоит в явном проверке Future, созданных с помощью точного Future конструктора, с которым вы тестируете.
> isFuture (resolve (42)) true > isFuture (42) false
никогда
never :: Future a b
Future, которое никогда не завершается. Может быть полезно в качестве начального значения при уменьшении с , например.
isNever
isNever :: a -> Boolean
Возвращает true, если заданный входной параметр является never.
extractLeft
extractLeft :: Future a b -> Array a
Возвращает массив, единственный элемент которого — причина отклонения Future. Во многих случаях получить это значение будет невозможно; в таких случаях массив будет пустым. Эта функция предназначена для интроспекции типа: она не является правильным способом .
extractRight
extractRight :: Future a b -> Array b
Возвращает массив, единственный элемент которого — значение разрешения Future. Во многих случаях получить это значение будет невозможно; в таких случаях массив будет пустым. Эта функция предназначена для интроспекции типа: она не является правильным способом .
debugMode
debugMode :: Boolean -> Undefined
Включить или выключить режим отладки Fluture. По умолчанию режим отладки выключен. Передайте true для включения или false для выключения.
debugMode (true)
Для получения дополнительной информации см. и .
контекст
Future.prototype.context :: Future a b ~> List Context
Связанный список контекстов отладки, доступный для каждого экземпляра Future. Когда выключен, список всегда пуст.
Объекты контекста имеют свойства stack, которые содержат снимки стеков вызовов, предшествовавших созданию экземпляра Future . Они используются Fluture для генерации контекстных стеков вызовов.
Лицензия
© 2020 Aldwin Vlasblom
Licensed under the MIT License.
https://github.com/fluture-js/Fluture/blob/14.0.0/README.md