liberty
liberty

Reputation: 413

Play Framework 2 - How can I await multiple WS responses without calling .get()?

A couple of the Play Framework devs have said "Never, ever call get() on a Promise because it can lead to deadlocks." Ok.

Say I have legacy code that expects Bar (not F.Promise<Bar>) and calls apply(Foo foo) below to get it. Within apply, I'd like to do some concurrent webservice calls, wait for the responses and then use them to make a Bar. How can I do that without calling futureBar.get(), again assuming that returning F.Promise<Bar> is not an option?

public class Func implements F.Function<Foo,Bar> {

    @Override
    public Bar apply(Foo foo) throws Throwable {
        F.Promise<WS.Response> response1 = WS.url("http://google.com").get();
        F.Promise<WS.Response> response2 = WS.url("http://yahoo.com").get();
        F.Promise<List<WS.Response>> responses = F.Promise.sequence(response1, response2);
        F.Promise<Bar> futureBar = responses.map(new F.Function<List<WS.Response>, Bar>() {

            @Override
            public Bar apply(List<WS.Response> o) throws Throwable {
                //some code;
                return bar;
            }
        });
        //How can I return Bar without calling get?
    }
}

Upvotes: 3

Views: 1673

Answers (2)

Ryan
Ryan

Reputation: 7257

Instead of returning a Bar, return a F.Promise<Bar>.

public class Func implements F.Function<Foo,F.Promise<Bar>> {

    @Override
    public F.Promise<Bar> apply(Foo foo) throws Throwable {
        F.Promise<WS.Response> response1 = WS.url("http://google.com").get();
        F.Promise<WS.Response> response2 = WS.url("http://yahoo.com").get();
        F.Promise<List<WS.Response>> responses = F.Promise.sequence(response1, response2);
        F.Promise<Bar> futureBar = responses.map(new F.Function<List<WS.Response>, Bar>() {

            @Override
            public Bar apply(List<WS.Response> o) throws Throwable {
                //some code;
                return bar;
            }
        });

        return futureBar;
    }
}

From there on, you operate on Bar within the Promise, using map, flatMap and filter as described in the Javadoc: http://www.playframework.com/documentation/2.2.x/api/java/play/libs/F.Promise.html

When working asynchronously, you get used to always dealing with your code in the context of a future, this is how you keep your code asynchronous.

In Scala, you would do this:

def apply(foo: Foo): Future[Bar] = {
  for {
    response1 <- WS.url("http://google.com").get
    response2 <- WS.url("http://yahoo.com").get
  } yield {
    // some code
    bar
  }
}

Upvotes: 1

Andrey Tyukin
Andrey Tyukin

Reputation: 44992

(I've never used Akka from Java, so do not fully understand the details of your code, but I hope that I understand your problem anyway)

Short answer: you cannot obtain Bar. And you should never call .get, because it either kills all the advantages of using Futures/Promises (in the best case), or, as already mentioned, leads to deadlocks and kills everything (in the worst case). Once you have stuff inside of a Future[-], it always stays in the Future[-], there is no way back. If you have legacy code that does this:

bar = Func.apply(foo);
doSomethingWithBar(bar)

and you suddenly find out that your Func is no longer synchronous (does not return immediately), but instead asynchronous (makes bunch of long requests), you have to accept that your new asynchronous version of Func cannot live together with the ';'

So you have to reprogram the ';'

Of course, you do not do it yourself, because you already have the Future[-] monad, so you just have to replace the old ';' by the new ';' i.e. map / flatMap, depending on whether your doSomethingWithBar is synchronous or not:

Func.apply(foo).map{ bar =>
  doSomethingWithBar(bar)
}

In general, if you previously had something like that:

y = asynch_func1(x);
z = asynch_func2(x, y);
w = synch_func3(x, y, z);
v = asynch_func4(x, y, z, w);
print(v)

and suddenly find out that your functions asynch_func1, asynch_func2, asynch_func4 are now asynchronous and take long time to complete (while synch_func3 remains synchronous, just as example), you rewrite it to:

asynch_func1(x).flatMap{ y =>
  asynch_func2(x, y).map{ z =>
    synch_func3(x,y,z).flatMap{ w =>
      v = asynch_func4(x, y, z, w)
      print(v)
    }
  }
}

I guess it's gonna look pretty horrible (without for-comprehensions and in Java-syntax...), but as long as you have the conceptual clarity and understanding that you are just replacing semicolons by map/flatMap, you should be able to adjust your legacy code pretty quickly and without (too) much headache.

I hope I've answered the right question.

Upvotes: 1

Related Questions