A couple years ago I spent a great deal of time in the waiting room of an allergy clinic. So much that I finally found the time to write a library that was meant to be a followup to libgtask/libiris libraries I wrote nearly two decades ago. A lot has changed in Linux since then and I felt that maybe this time, I could get it “right”.
  
  
   This will be a multi-part series, but today lets focus on terminology so we have a common language to communicate.
  
  
   Futures
  
  
   A future is a container that will eventually contain a result or an error.
  
  
   Programmers often use the words “future” and “promise” interchangeably. Libdex tries, when possible, to follow the academic nomenclature for futures. That is to say that a future is the interface and promise is a type of future.
  
  
   Futures exist in one of three states. The first state is
   
    pending
   
   . A future exists in this state until it has either rejected or resolved.
  
  
   The second state is
   
    resolved
   
   . A future reaches this state when it has successfully obtained a value.
  
  
   The last third state is
   
    rejected
   
   . If there was a failure to obtain a value a future will be in this state and contain a
   
    GError
   
   representing such failure.
  
  
   Promises and More
  
  
   A promise is a type of future that allows the creator to set the resolved value or error. This is a common type of future to use when you are integrating with things that are not yet integrated with Libdex.
  
  
   Other types of futures also exist.
  
  /* resolve to "true" */
DexPromise *good = dex_promise_new ();
dex_promise_resolve_boolean (good, TRUE);
/* reject with error */
DexPromise *bad = dex_promise_new ();
dex_promise_reject (good,
                    g_error_new (G_IO_ERROR,
                                 G_IO_ERROR_FAILED,
                                 "Failed"));
  
   Static Futures
  
  
   Sometimes you already know the result of a future upfront.
   
   The
   
    DexStaticFuture
   
   is used in this case.
   
   Various constructors for
   
    DexFuture
   
   will help you create one.
  
  
   For example,
   
    Dex.Future.new_take_object()
   
   will create a static future for a
   
    GObject
   
   -derived instance.
  
  DexFuture *future = dex_future_new_for_int (123);
  
   Blocks
  
  
   One of the most commonly used types of futures in Libdex is a
   
    DexBlock
   
   .
  
  
   A
   
    DexBlock
   
   is a callback that is run to process the result of a future. The block itself is also a future meaning that you can chain these blocks together into robust processing groups.
  
  
   “Then” Blocks
  
  
   The first type of block is a “then” block which is created using
   
    Dex.Future.then()
   
   . These blocks will only be run if the dependent future resolves with a value. Otherwise, the rejection of the dependent future is propagated to the block.
  
  static DexFuture *
further_processing (DexFuture *future,
                    gpointer   user_data)
{
  const GValue *result = dex_promise_get_value (future, NULL);
  /* since future is completed at this point, you can also use
   * the simplified "await" API. Otherwise you'd get a rejection
   * for not being on a fiber. (More on that later).
   */
  g_autoptr(GObject) object = dex_await_object (dex_ref (future), NULL);
  return dex_ref (future);
}
  
   “Catch” Blocks
  
  
   Since some futures may fail, there is value in being able to “catch” the failure and resolve it.
  
  
   Use
   
    Dex.Future.catch()
   
   to handle the result of a rejected future and resolve or reject it further.
  
  static DexFuture *
catch_rejection (DexFuture *future,
                 gpointer   user_data)
{
  g_autoptr(GError) error = NULL;
  dex_future_get_value (future, &error);
  if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND))
    return dex_future_new_true ();
  return dex_ref (future);
}
  
   “Finally” Blocks
  
  
   There may be times when you want to handle completion of a future whether it resolved or rejected. For this case, use a “finally” block by calling
   
    Dex.Future.finally()
   
   .
  
  
   Infinite Loops
  
  
   If you find you have a case where you want a
   
    DexBlock
   
   to loop indefinitely, you can use the
   
    _loop
   
   variants of the block APIs.
  
  
   See
   
    Dex.Future.then_loop()
   
   ,
   
    Dex.Future.catch_loop()
   
   , or
   
    Dex.Future.finally_loop()
   
   . This is generally useful when your block’s callback will begin the next stage of work as the result of the callback.
  
  
   Future Sets
  
  
   A
   
    FutureSet
   
   is a type of future that is the composition of multiple futures. This is an extremely useful construct because it allows you to do work concurrently and then process the results in a sort of “reduce” phase.
  
  
   For example, you could make a request to a database, cache server, and a timeout and process the first that completes.
  
  
   There are multiple types of future sets based on the type of problem you want to solve.
  
  
   
    Dex.Future.all()
   
   can be used to resolve when all dependent futures have resolved, otherwise it will reject with error once they are complete.
   
   If you want to reject as soon as the first item rejects,
   
    Dex.Future.all_race()
   
   will get you that behavior.
  
  
   Other useful
   
    Dex.FutureSet
   
   construtors include
   
    Dex.Future.any()
   
   and
   
    Dex.Future.first
   
   .
  
  /* Either timeout or propagate result of cache/db query */
return dex_future_first (dex_timeout_new_seconds (60),
                         dex_future_any (query_db_server (),
                                         query_cache_server (),
                                         NULL),
                         NULL);
  
   Cancellable
  
  
   Many programmers who use GTK and GIO are familiar with
   
    GCancellable
   
   . Libdex has something similar in the form of
   
    DexCancellable
   
   . However, in the Libdex case,
   
    DexCancellable
   
   is a future.
  
  
   It allows for convenient grouping with other futures to perform cancellation when the
   
    Dex.Cancellable.cancel()
   
   method is called.
  
  
   It can also integrate with
   
    GCancellable
   
   when created using
   
    Dex.Cancellable.new_from_cancellable()
   
   .
  
  
   A
   
    DexCancellable
   
   will only ever reject.
  
  DexFuture *future = dex_cancellable_new ();
dex_cancellable_cancel (DEX_CANCELLABLE (future));
  
   Timeouts
  
  
   A timeout may be represented as a future.
   
   In this case, the timeout will reject after a time period has passed.
  
  
   A
   
    DexTimeout
   
   will only ever reject.
  
  
   This future is implemented ontop of
   
    GMainContext
   
   via API like
   
    g_timeout_add()
   
   .
  
  DexFuture *future = dex_timeout_new_seconds (60);
  
   Unix Signals
  
  
   Libdex can represent unix signals as a future. That is to say that the future will resolve to an integer of the signal number when that signal has been raised.
  
  
   This is implemented using
   
    g_unix_signal_source_new()
   
   and comes with all the same restrictions.
  
  
   Delayed
  
  
   Sometimes you may run into a case where you want to gate the result of a future until a specific moment.
  
  
   For this case,
   
    DexDelayed
   
   allows you to wrap another future and decide when to “uncork” the result.
  
  DexFuture *delayed = dex_delayed_new (dex_future_new_true ());
dex_delayed_release (DEX_DELAYED (delayed));
  
   Fibers
  
  
   Another type of future is a “fiber”.
  
  
   More care will be spent on fibers later on but suffice to say that the result of a fiber is easily consumable as a future via
   
    DexFiber
   
   .
  
  DexFuture *future = dex_scheduler_spawn (NULL, 0, my_fiber, state, state_free);
  
   Cancellation Propagation
  
  
   Futures within your application will enevitably depend on other futures.
  
  
   If all of the futures depending on a future have been released, the dependent future will have the opportunity to cancel itself. This allows for cascading cancellation so that unnecessary work may be elided.
  
  
   You can use
   
    Dex.Future.disown()
   
   to ensure that a future will continue to be run even if the dependent futures are released.
  
  
   Schedulers
  
  
   Libdex requires much processing that needs to be done on the main loop of a thread. This is generally handled by a
   
    DexScheduler
   
   .
  
  
   The main thread of an application has the default sheduler which is a
   
    DexMainScheduler
   
   .
  
  
   Libdex also has a managed thread pool of schedulers via the
   
    DexThreadPoolScheduler
   
   .
  
  
   Schedulers manage short tasks, executing
   
    DexBlock
   
   when they are ready, finalizing objects on their owning thread, and running fibers.
  
  
   Schedulers integrate with the current threads
   
    GMainContext
   
   via
   
    GSource
   
   making it easy to use Libdex with GTK and Clutter-based applications.
  
  
   Channels
  
  
   
    DexChannel
   
   is a higher-level construct built on futures that allow passing work between producers and consumers. They are akin to Go channels in that they have a read and a write side. However, they are much more focused on integrating well with
   
    DexFuture
   
   .
  
  
   You can find this article in the Libdex documentation under
   
    terminology
   
   .