futures::chain
authorHans Fugal <fugalh@fb.com>
Tue, 10 Feb 2015 20:24:07 +0000 (12:24 -0800)
committerSara Golemon <sgolemon@fb.com>
Wed, 11 Feb 2015 02:02:00 +0000 (18:02 -0800)
Summary:
`futures::chain<A,Z>(a, b, c, d, e, f, ..., z)` where `a` is a callback suitable for `Future<A>::then` and `z` is a callback suitable for `Future<Y>::then<Z>`.

This will be important, at least in the background, for the new `via`. It will probably also be useful to some user sometime.

I imagine this will be tweaked over time if people find magic ways to get it to deduce the types better. But this works and it's not *too* much trouble to specify A and Z.

Test Plan: unit

Reviewed By: davejwatson@fb.com

Subscribers: trunkagent, exa, folly-diffs@, yfeldblum, jsedgwick

FB internal diff: D1831073

Tasks: 6048744

Signature: t1:1831073:1423259292:711be0e047a2acb706fd74e529d5d5fb6abda566

folly/futures/Future-inl.h
folly/futures/Future.h
folly/futures/test/ViaTest.cpp

index 21a8134d8e99cd681404302727dc3cd8b7ada9d9..578646819fd1033692952482b7932054e31abac8 100644 (file)
@@ -768,8 +768,38 @@ Future<T>&& Future<T>::waitVia(DrivableExecutor* e) && {
   return std::move(*this);
 }
 
+namespace futures {
+
+  namespace {
+    template <class Z, class F, class... Callbacks>
+    Future<Z> chainHelper(F, Callbacks...);
+
+    template <class Z>
+    Future<Z> chainHelper(Future<Z> f) {
+      return f;
+    }
+
+    template <class Z, class F, class Fn, class... Callbacks>
+    Future<Z> chainHelper(F f, Fn fn, Callbacks... fns) {
+      return chainHelper<Z>(f.then(fn), fns...);
+    }
+  }
+
+  template <class A, class Z, class... Callbacks>
+  std::function<Future<Z>(Try<A>)>
+  chain(Callbacks... fns) {
+    MoveWrapper<Promise<A>> pw;
+    MoveWrapper<Future<Z>> fw(chainHelper<Z>(pw->getFuture(), fns...));
+    return [=](Try<A> t) mutable {
+      pw->fulfilTry(std::move(t));
+      return std::move(*fw);
+    };
+  }
+
 }
 
+} // namespace folly
+
 // I haven't included a Future<T&> specialization because I don't forsee us
 // using it, however it is not difficult to add when needed. Refer to
 // Future<void> for guidance. std::future and boost::future code would also be
index 695c528207e2651772ec07aaf2cfe6440971f4ea..c018a9e7806f7514b7a1ac57be3e416807478b6b 100644 (file)
@@ -166,6 +166,19 @@ namespace futures {
   /// needed. If your program never uses any timeouts or other time-based
   /// Futures you will pay no Timekeeper thread overhead.
   Future<void> sleep(Duration, Timekeeper* = nullptr);
+
+  /// Create a Future chain from a sequence of callbacks. i.e.
+  ///
+  ///   f.then(a).then(b).then(c);
+  ///
+  /// where f is a Future<A> and the result of the chain is a Future<Z>
+  /// becomes
+  ///
+  ///   f.then(chain<A,Z>(a, b, c));
+  // If anyone figures how to get chain to deduce A and Z, I'll buy you a drink.
+  template <class A, class Z, class... Callbacks>
+  std::function<Future<Z>(Try<A>)>
+  chain(Callbacks... fns);
 }
 
 template <class T>
index 5a76f3c14628580d1b63fb2ebbca331e3967bed7..5cb66827879122038f8b007a75c5eb35de37fe2d 100644 (file)
@@ -183,3 +183,20 @@ TEST_F(ViaFixture, viaAssignment) {
   // via()&
   auto f2 = f.via(eastExecutor.get());
 }
+
+TEST(Via, chain1) {
+  EXPECT_EQ(42,
+            makeFuture()
+            .then(futures::chain<void, int>([] { return 42; }))
+            .get());
+}
+
+TEST(Via, chain3) {
+  int count = 0;
+  auto f = makeFuture().then(futures::chain<void, int>(
+      [&]{ count++; return 3.14159; },
+      [&](double) { count++; return std::string("hello"); },
+      [&]{ count++; return makeFuture(42); }));
+  EXPECT_EQ(42, f.get());
+  EXPECT_EQ(3, count);
+}