Skip to content

Comparison of Future Implementations

Aldwin Vlasblom edited this page Feb 26, 2022 · 38 revisions

Outdated

⚠️ This wiki page was useful back when Fluture was created, and there were several implementations out there. The contents on this page are still true, but not as relevant any more, for the following reasons:

  • Ramda Fantasy has been discontinued.
  • Folktale's implementation is diverging.
  • FunTask's author helped integrate its features into Fluture.

The new alternatives to Fluture that have been called into existence differ in fundamental ways, and it doesn't make as much sense to compare them like the table below does. Even Folktale already balances on this line.

Some new alternatives:


Overview

This is a table of the most popular Future-like libraries and the things they do and don't do. It is my intention to keep this list updated with features that other libraries implement, as well as the ones that Fluture does. If you notice a discrepancy, feel free to report it.

You may also be interested to see a more practical comparison. This exists in the form of the async problem.

Feature Fluture Folktale FunTask Ramda Fantasy
Cancellation βœ” βœ” βœ” ❌
Stack safety βœ” ❌ ❌ ❌
Continuation guarding βœ” βœ” βœ” ❌
Resource management βœ” βœ” ❌ ❌
Nice errors βœ” ❌ ❌ ❌
High performance βœ” ❌ ❌ ❌
Automatic exception catching βœ” ❌ βœ” ❌
Automatic caching ❌ βœ” ❌ ❌

βœ” Cancellation

Fluture allows running computations to be canceled. These computations can abort whatever they were doing, clearing the event loop and memory. Fluture automatically cancels computations which are no longer necessary, like the loser of a Future.race.

βœ” Stack safety

Fluture interprets the lazy computation with constant stack usage which means that no matter how big your computation might get it memory, running it will never cause it to blow the stack.

βœ” Continuation guarding

When a computation attempts to settle multiple times, Fluture ignores it automatically. A computation can only ever resolve once.

βœ” Resource management

Future.hook allows for resources to be safely disposed after use, and it ties in with cancellation to ensure resources are also disposed when the computation is canceled.

βœ” Nice errors

Fluture performs type-checking of almost all user input, in order to ensure that any mistake is quickly discovered through an informative error message:

Future.of('world').chain(thing => `Hello ${thing}!`).value(console.log)
TypeError: Future#chain expects the function it's given to return a Future.
  Actual: "Hello world!" :: String
  From calling: thing => `Hello ${thing}!`
  With: "world"

βœ” High performance

Despite being lazy, supporting cancellation, and performing type-checking, Fluture's Futures perform roughly half as well as native Promises.

βœ” Automatic exception catching

As of version 9.0.0, Fluture provides forkCatch to allow exceptions to be handled. The original fork function still causes exceptions to be thrown.

❌ Automatic caching

Futures will re-run their computation every time they are forked, unless the Future is cached using Future.cache.

βœ” Cancellation

When you run a Folktale Task, it gives you back a TaskExecution on which one may call cancel, which triggers the cancellation handlers on the original Task to be called.

❌ Stack safety

Folktale's Tasks and Futures use function composition as the basis for transformations. Every time you transform the structure, its rejection, resolution, and cancellation handlers will have higher stack usage, putting the user in potential danger to Stack Overflow Exceptions.

βœ” Continuation guarding

When a computation attempts to settle multiple times, Folktale throws an Error. This differs from Fluture, which ignores multiple rejections/resolutions. Folktale does guard against it, but moves the responsibility of stopping it from happening to its users. This can be good, because it may lead to a user catching a bug they wouldn't otherwise have noticed. Solving the bug would almost always involve implementing the same pattern though, which Fluture has included by default.

βœ” Resource management

Folktale TaskExecution allows for the registration of cleanup handlers. When a Folktale Task is rejected, resolved, or canceled, it runs all the cleanup handlers.

❌ Nice errors

Folktale Tasks fail with weird error messages if the user makes a mistake:

data.task.of('world').chain(thing => `Hello ${thing}!`).run()
TypeError: transformation(...).run is not a function

That said, they have plans to move to TypeScript, which should prevent some of these mistakes from ever being made.

❌ Automatic exception catching

Same approach as Fluture.

❌ High performance

Folktale tasks are roughly three times as slow as native Promises, and roughly six times as slow as Fluture.

βœ” Automatic caching

When a Folktale task is run, the returned TaskExecution automatically caches the result for you, so that multiple conversions to Folktale Future or Promise yield the same result without having to run the computation again.

βœ” Cancellation

Same approach as Fluture.

❌ Stack safety

Same approach as Folktale.

βœ” Continuation guarding

Same approach as Fluture.

❌ Resource management

FunTask provides no mechanisms for resource disposal.

❌ Nice errors

Same approach as Folktale.

❌ High performance

About ten times as slow as Fluture.

βœ” Automatic exception catching

FunTask can catch thrown errors for you, and separates them to a third branch. This allows users to handle bugs gracefully, and separately from expected failures.

❌ Automatic caching

Same approach as Fluture.

❌ Cancellation

Ramda Futures cannot be canceled.

❌ Stack safety

Same approach as Folktale.

❌ Continuation guarding

When a Ramda Future is resolved multiple times, its transformations and completion handlers are called multiple times as well, possibly leading to unexpected results.

❌ Resource management

Ramda Fantasy provides no mechanisms for resource disposal.

❌ Nice errors

Mistakes made by users are not detected, often leading to errors like f(...)._fork is not a function. What's worse, because exceptions are automatically caught it can happen that they end up in the rejection branch, becoming indistinguishable from expected failures.

❌ High performance

About three times as slow as Fluture.

❌ Automatic exception catching

Ramda Fantasy does catch exceptions, but it places the Error objects in the rejection branch of the Future. This potentially causes the type of a in Future a b to become a mixed type like String | Error, leading to users having to write weird polymorphic code. It also causes bugs to become indistinguishable from expected failures.

❌ Automatic caching

Same approach as Fluture.