ff3a91e04a7fc7c7d977cc3e78a886e161f020ff
[m6w6/seekat] / lib / API / Invoker.php
1 <?php
2
3 namespace seekat\API;
4
5 use Generator;
6 use http\Client;
7 use React\Promise\Deferred;
8 use React\Promise\PromiseInterface;
9 use React\Promise\ExtendedPromiseInterface;
10
11 use function React\Promise\all;
12
13 class Invoker extends Deferred
14 {
15 /**
16 * The HTTP client
17 * @var Client
18 */
19 private $client;
20
21 /**
22 * The return value of the generator
23 * @var mixed
24 */
25 private $result;
26
27 /**
28 * Cancellation flag
29 * @var bool
30 */
31 private $cancelled = false;
32
33 /**
34 * Create a new generator invoker
35 * @param \http\Client $client
36 */
37 function __construct(Client $client) {
38 $this->client = $client;
39
40 parent::__construct(function($resolve, $reject) {
41 return $this->cancel($resolve, $reject);
42 });
43 }
44
45 /**
46 * Invoke $generator to create a \Generator which yields promises
47 *
48 * @param callable $generator as function() : \Generator, creating a generator yielding promises
49 * @return \seekat\API\Invoker
50 */
51 function invoke(callable $generator) : Invoker {
52 $this->iterate($generator());
53 return $this;
54 }
55
56 /**
57 * Iterate over $gen, a \Generator yielding promises
58 *
59 * @param \Generator $gen
60 * @return \seekat\API\Invoker
61 */
62 function iterate(Generator $gen) : Invoker {
63 $this->cancelled = false;
64
65 foreach ($gen as $promise) {
66 if ($this->cancelled) {
67 break;
68 }
69 $this->queue($promise, $gen);
70 }
71
72 if (!$this->cancelled) {
73 $this->resolve($this->result = $gen->getReturn());
74 }
75 return $this;
76 }
77
78 /**
79 * Get the generator's result
80 *
81 * @return \React\Promise\ExtendedPromiseInterface
82 */
83 function result() : ExtendedPromiseInterface {
84 return $this->promise();
85 }
86
87 /**
88 * Promise handler
89 *
90 * @param \React\Promise\PromiseInterface $promise
91 * @param \Generator $to
92 */
93 private function give(PromiseInterface $promise, Generator $to) {
94 $promise->then(function($result) use($to) {
95 if (($promise = $to->send($result))) {
96 $this->queue($promise, $to);
97 }
98 });
99 }
100
101 private function queue($promise, Generator $gen) {
102 if ($promise instanceof PromiseInterface) {
103 $this->give($promise, $gen);
104 } else {
105 all($promise)->then(function($results) use($gen) {
106 if (($promise = $gen->send($results))) {
107 $this->queue($promise, $gen);
108 }
109 });
110 }
111 $this->client->send();
112 }
113
114 /**
115 * Cancellation callback
116 *
117 * @param callable $resolve
118 * @param callable $reject
119 */
120 private function cancel(callable $resolve, callable $reject) {
121 $this->cancelled = true;
122
123 /* did we finish in the meantime? */
124 if ($this->result) {
125 $resolve($this->result);
126 } else {
127 $reject("Cancelled");
128 }
129 }
130 }