Tutorial » Connecting continuables

Explains how to connect various continuable_base objects together.

Connections and strategies

Connections make it possible to describe the dependencies between an arbitrary count of continuable_base objects in order resolve a returned continuable_base as soon as the dependencies are fulfilled.

For each connection strategy continuable_base provides an operator for for instance continuable_base::operator && and a free function, when_all for example. Both work similar however the free functions are capable of working with nested sequences as described in Nested continuables and plain types.

Using aggregated strategies

Aggregated strategies will call the result handler with the compound result of all connected continuable_base objects.

The compound result is deduced as following. Every continuable_base maps its result to the result itself as shown below. When multiple continuable_base objects are connected on the same depth, the result is joined. See Nested continuables and plain types for details.

Continuation typeIn tuple likeIn container (std::vector)
continuable_base with <><none><none>
continuable_base with <Arg>ArgArg
continuable_base with <Args...>Args...std::tuple<Args...>

Using the all connection

The all strategy invokes all connected continuable at once, it tries to resolve the connected continuable_base objects as fast as possible. It is possible to connect multiple continuable_base objects together through the all strategy by using continuable_base::operator && or when_all. In contrast to the operator the free functions are capable of workin with plain types and deeply nested continuable_base objects as described in Nested continuables and plain types .

(http_request("github.com") && http_request("travis-ci.org") &&
 http_request("atom.io"))
  .then([](std::string github, std::string travis,
           std::string atom) {
    // The callback is called with the
    // response of github, travis and atom.
  });

Using the sequential connection

The sequential strategy invokes all connected continuable one after each other, it tries to resolve the next connected continuable_base objects as soon as the previous one was resolved. It is possible to connect multiple continuable_base objects together through the sequential strategy by using continuable_base::operator>> or when_seq.

(http_request("github.com") >> http_request("travis-ci.org") >>
 http_request("atom.io"))
  .then([](std::string github, std::string travis,
           std::string atom) {
    // The requests are invoked sequentially instead
    // of requesting all at once.
  });

Using the any connection

The any connection strategy is completely different from the two introduces before: It calls the result handler with the first result or exception available. All continuable_base objects are required to have the same types of arguments.

(http_request("github.com") || http_request("travis-ci.org") ||
 http_request("atom.io"))
  .then([](std::string github_or_travis_or_atom) {
    // The callback is called with the first response
    // of either github, travis or atom.
  });

Mixing different strategies

Mixing different strategies through operators and free functions is natively supported as shown below:

(http_request("github.com") &&
 (http_request("travis-ci.org") || http_request("atom.io")))
    .then([](std::string github, std::string travis_or_atom) {
      // The callback is called with the response of
      // github for sure and the second parameter represents
      // the response of travis or atom.
    });

Nested continuables and plain types

For every operator that was shown above, there exists a free function that provides at least the same functionality:

cti::when_all(http_request("github.com"), http_request("travis-ci.org"));
cti::when_any(http_request("github.com"), http_request("travis-ci.org"));
cti::when_seq(http_request("github.com"), http_request("travis-ci.org"));

Additionally the free functions are capable of working with continuables deeply nested inside tuple like objects (std::tuple, std::pair and std::array) as well as homogeneous containers (std::vector, std::list etc.).

std::tuple<std::vector<cti::continuable<int>>> outstanding;
// ...

cti::when_all(std::make_tuple(std::move(outstanding),
                              http_request("github.com")))
      .then([](std::tuple<std::tuple<std::vector<int>>,
                          std::string> result) {
        // ...
      });

Values which are given to such a free function are preserved and later passed to the result handler:

cti::when_seq(0, 1,
              cti::make_ready_continuable(2, 3),
              4, 5)
      .then([](int r0, int r1, int r2,
               int r3, int r4) {
        // ...
      });

When combining both capabilities it's even possible do something like this:

cti::when_all(
    cti::make_ready_continuable(0, 1),
    2, //< See this plain value
    cti::populate(cti::make_ready_continuable(3),  // Creates a runtime
                  cti::make_ready_continuable(4)), // sized container.
    std::make_tuple(std::make_tuple(cti::make_ready_continuable(5))))
      .then([](int r0, int r1, int r2, std::vector<int> r34,
               std::tuple<std::tuple<int>> r5) {
        // ...
      });

Populating a container from arbitrary continuables

populate mainly helps to create a homogeneous container from a runtime known count of continuables which type isn't exactly known. All continuables which are passed to this function should be originating from the same source or a method called with the same types of arguments:

// cti::populate just creates a std::vector from the two continuables.
auto v = cti::populate(cti::make_ready_continuable(0),
                       cti::make_ready_continuable(1));

for (int i = 2; i < 5; ++i) {
  // It is possible to  add more continuables
  // to the container afterwards
  container.emplace_back(cti::make_ready_continuable(i));
}

cti::when_all(std::move(v))
  .then([](std::vector<int> resolved) {
    // ...
  });