1<?php
2namespace GuzzleHttp\Ring\Future;
3
4use GuzzleHttp\Ring\Exception\CancelledFutureAccessException;
5use GuzzleHttp\Ring\Exception\RingException;
6use React\Promise\PromiseInterface;
7
8/**
9 * Implements common future functionality built on top of promises.
10 */
11trait BaseFutureTrait
12{
13    /** @var callable */
14    private $waitfn;
15
16    /** @var callable */
17    private $cancelfn;
18
19    /** @var PromiseInterface */
20    private $wrappedPromise;
21
22    /** @var \Exception Error encountered. */
23    private $error;
24
25    /** @var mixed Result of the future */
26    private $result;
27
28    private $isRealized = false;
29
30    /**
31     * @param PromiseInterface $promise Promise to shadow with the future.
32     * @param callable         $wait    Function that blocks until the deferred
33     *                                  computation has been resolved. This
34     *                                  function MUST resolve the deferred value
35     *                                  associated with the supplied promise.
36     * @param callable         $cancel  If possible and reasonable, provide a
37     *                                  function that can be used to cancel the
38     *                                  future from completing.
39     */
40    public function __construct(
41        PromiseInterface $promise,
42        callable $wait = null,
43        callable $cancel = null
44    ) {
45        $this->wrappedPromise = $promise;
46        $this->waitfn = $wait;
47        $this->cancelfn = $cancel;
48    }
49
50    /**
51     * @return mixed
52     */
53    public function wait()
54    {
55        if (!$this->isRealized) {
56            $this->addShadow();
57            if (!$this->isRealized && $this->waitfn) {
58                $this->invokeWait();
59            }
60            if (!$this->isRealized) {
61                $this->error = new RingException('Waiting did not resolve future');
62            }
63        }
64
65        if ($this->error) {
66            throw $this->error;
67        }
68
69        return $this->result;
70    }
71
72    /**
73     * @return PromiseInterface
74     */
75    public function promise()
76    {
77        return $this->wrappedPromise;
78    }
79
80    /**
81     * @return PromiseInterface
82     */
83    public function then(
84        callable $onFulfilled = null,
85        callable $onRejected = null,
86        callable $onProgress = null
87    ) {
88        return $this->wrappedPromise->then($onFulfilled, $onRejected, $onProgress);
89    }
90
91    public function cancel()
92    {
93        if (!$this->isRealized) {
94            $cancelfn = $this->cancelfn;
95            $this->waitfn = $this->cancelfn = null;
96            $this->isRealized = true;
97            $this->error = new CancelledFutureAccessException();
98            if ($cancelfn) {
99                $cancelfn($this);
100            }
101        }
102    }
103
104    private function addShadow()
105    {
106        // Get the result and error when the promise is resolved. Note that
107        // calling this function might trigger the resolution immediately.
108        $this->wrappedPromise->then(
109            function ($value) {
110                $this->isRealized = true;
111                $this->result = $value;
112                $this->waitfn = $this->cancelfn = null;
113            },
114            function ($error) {
115                $this->isRealized = true;
116                $this->error = $error;
117                $this->waitfn = $this->cancelfn = null;
118            }
119        );
120    }
121
122    private function invokeWait()
123    {
124        try {
125            $wait = $this->waitfn;
126            $this->waitfn = null;
127            $wait();
128        } catch (\Exception $e) {
129            // Defer can throw to reject.
130            $this->error = $e;
131            $this->isRealized = true;
132        }
133    }
134}
135