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    public function wait()
51    {
52        if (!$this->isRealized) {
53            $this->addShadow();
54            if (!$this->isRealized && $this->waitfn) {
55                $this->invokeWait();
56            }
57            if (!$this->isRealized) {
58                $this->error = new RingException('Waiting did not resolve future');
59            }
60        }
61
62        if ($this->error) {
63            throw $this->error;
64        }
65
66        return $this->result;
67    }
68
69    public function promise()
70    {
71        return $this->wrappedPromise;
72    }
73
74    public function then(
75        callable $onFulfilled = null,
76        callable $onRejected = null,
77        callable $onProgress = null
78    ) {
79        return $this->wrappedPromise->then($onFulfilled, $onRejected, $onProgress);
80    }
81
82    public function cancel()
83    {
84        if (!$this->isRealized) {
85            $cancelfn = $this->cancelfn;
86            $this->waitfn = $this->cancelfn = null;
87            $this->isRealized = true;
88            $this->error = new CancelledFutureAccessException();
89            if ($cancelfn) {
90                $cancelfn($this);
91            }
92        }
93    }
94
95    private function addShadow()
96    {
97        // Get the result and error when the promise is resolved. Note that
98        // calling this function might trigger the resolution immediately.
99        $this->wrappedPromise->then(
100            function ($value) {
101                $this->isRealized = true;
102                $this->result = $value;
103                $this->waitfn = $this->cancelfn = null;
104            },
105            function ($error) {
106                $this->isRealized = true;
107                $this->error = $error;
108                $this->waitfn = $this->cancelfn = null;
109            }
110        );
111    }
112
113    private function invokeWait()
114    {
115        try {
116            $wait = $this->waitfn;
117            $this->waitfn = null;
118            $wait();
119        } catch (\Exception $e) {
120            // Defer can throw to reject.
121            $this->error = $e;
122            $this->isRealized = true;
123        }
124    }
125}
126