xref: /plugin/davcal/vendor/sabre/event/lib/Promise.php (revision a1a3b6794e0e143a4a8b51d3185ce2d339be61ab)
1*a1a3b679SAndreas Boehler<?php
2*a1a3b679SAndreas Boehler
3*a1a3b679SAndreas Boehlernamespace Sabre\Event;
4*a1a3b679SAndreas Boehler
5*a1a3b679SAndreas Boehleruse Exception;
6*a1a3b679SAndreas Boehler
7*a1a3b679SAndreas Boehler/**
8*a1a3b679SAndreas Boehler * An implementation of the Promise pattern.
9*a1a3b679SAndreas Boehler *
10*a1a3b679SAndreas Boehler * Promises basically allow you to avoid what is commonly called 'callback
11*a1a3b679SAndreas Boehler * hell'. It allows for easily chaining of asynchronous operations.
12*a1a3b679SAndreas Boehler *
13*a1a3b679SAndreas Boehler * @copyright Copyright (C) 2013-2015 fruux GmbH (https://fruux.com/).
14*a1a3b679SAndreas Boehler * @author Evert Pot (http://evertpot.com/)
15*a1a3b679SAndreas Boehler * @license http://sabre.io/license/ Modified BSD License
16*a1a3b679SAndreas Boehler */
17*a1a3b679SAndreas Boehlerclass Promise {
18*a1a3b679SAndreas Boehler
19*a1a3b679SAndreas Boehler    /**
20*a1a3b679SAndreas Boehler     * Pending promise. No result yet.
21*a1a3b679SAndreas Boehler     */
22*a1a3b679SAndreas Boehler    const PENDING = 0;
23*a1a3b679SAndreas Boehler
24*a1a3b679SAndreas Boehler    /**
25*a1a3b679SAndreas Boehler     * The promise has been fulfilled. It was successful.
26*a1a3b679SAndreas Boehler     */
27*a1a3b679SAndreas Boehler    const FULFILLED = 1;
28*a1a3b679SAndreas Boehler
29*a1a3b679SAndreas Boehler    /**
30*a1a3b679SAndreas Boehler     * The promise was rejected. The operation failed.
31*a1a3b679SAndreas Boehler     */
32*a1a3b679SAndreas Boehler    const REJECTED = 2;
33*a1a3b679SAndreas Boehler
34*a1a3b679SAndreas Boehler    /**
35*a1a3b679SAndreas Boehler     * The current state of this promise.
36*a1a3b679SAndreas Boehler     *
37*a1a3b679SAndreas Boehler     * @var int
38*a1a3b679SAndreas Boehler     */
39*a1a3b679SAndreas Boehler    protected $state = self::PENDING;
40*a1a3b679SAndreas Boehler
41*a1a3b679SAndreas Boehler    /**
42*a1a3b679SAndreas Boehler     * A list of subscribers. Subscribers are the callbacks that want us to let
43*a1a3b679SAndreas Boehler     * them know if the callback was fulfilled or rejected.
44*a1a3b679SAndreas Boehler     *
45*a1a3b679SAndreas Boehler     * @var array
46*a1a3b679SAndreas Boehler     */
47*a1a3b679SAndreas Boehler    protected $subscribers = [];
48*a1a3b679SAndreas Boehler
49*a1a3b679SAndreas Boehler    /**
50*a1a3b679SAndreas Boehler     * The result of the promise.
51*a1a3b679SAndreas Boehler     *
52*a1a3b679SAndreas Boehler     * If the promise was fulfilled, this will be the result value. If the
53*a1a3b679SAndreas Boehler     * promise was rejected, this is most commonly an exception.
54*a1a3b679SAndreas Boehler     *
55*a1a3b679SAndreas Boehler     * @var mixed
56*a1a3b679SAndreas Boehler     */
57*a1a3b679SAndreas Boehler    protected $value = null;
58*a1a3b679SAndreas Boehler
59*a1a3b679SAndreas Boehler    /**
60*a1a3b679SAndreas Boehler     * Creates the promise.
61*a1a3b679SAndreas Boehler     *
62*a1a3b679SAndreas Boehler     * The passed argument is the executor. The executor is automatically
63*a1a3b679SAndreas Boehler     * called with two arguments.
64*a1a3b679SAndreas Boehler     *
65*a1a3b679SAndreas Boehler     * Each are callbacks that map to $this->fulfill and $this->reject.
66*a1a3b679SAndreas Boehler     * Using the executor is optional.
67*a1a3b679SAndreas Boehler     *
68*a1a3b679SAndreas Boehler     * @param callable $executor
69*a1a3b679SAndreas Boehler     */
70*a1a3b679SAndreas Boehler    function __construct(callable $executor = null) {
71*a1a3b679SAndreas Boehler
72*a1a3b679SAndreas Boehler        if ($executor) {
73*a1a3b679SAndreas Boehler            $executor(
74*a1a3b679SAndreas Boehler                [$this, 'fulfill'],
75*a1a3b679SAndreas Boehler                [$this, 'reject']
76*a1a3b679SAndreas Boehler            );
77*a1a3b679SAndreas Boehler        }
78*a1a3b679SAndreas Boehler
79*a1a3b679SAndreas Boehler    }
80*a1a3b679SAndreas Boehler
81*a1a3b679SAndreas Boehler    /**
82*a1a3b679SAndreas Boehler     * This method allows you to specify the callback that will be called after
83*a1a3b679SAndreas Boehler     * the promise has been fulfilled or rejected.
84*a1a3b679SAndreas Boehler     *
85*a1a3b679SAndreas Boehler     * Both arguments are optional.
86*a1a3b679SAndreas Boehler     *
87*a1a3b679SAndreas Boehler     * This method returns a new promise, which can be used for chaining.
88*a1a3b679SAndreas Boehler     * If either the onFulfilled or onRejected callback is called, you may
89*a1a3b679SAndreas Boehler     * return a result from this callback.
90*a1a3b679SAndreas Boehler     *
91*a1a3b679SAndreas Boehler     * If the result of this callback is yet another promise, the result of
92*a1a3b679SAndreas Boehler     * _that_ promise will be used to set the result of the returned promise.
93*a1a3b679SAndreas Boehler     *
94*a1a3b679SAndreas Boehler     * If either of the callbacks return any other value, the returned promise
95*a1a3b679SAndreas Boehler     * is automatically fulfilled with that value.
96*a1a3b679SAndreas Boehler     *
97*a1a3b679SAndreas Boehler     * If either of the callbacks throw an exception, the returned promise will
98*a1a3b679SAndreas Boehler     * be rejected and the exception will be passed back.
99*a1a3b679SAndreas Boehler     *
100*a1a3b679SAndreas Boehler     * @param callable $onFulfilled
101*a1a3b679SAndreas Boehler     * @param callable $onRejected
102*a1a3b679SAndreas Boehler     * @return Promise
103*a1a3b679SAndreas Boehler     */
104*a1a3b679SAndreas Boehler    function then(callable $onFulfilled = null, callable $onRejected = null) {
105*a1a3b679SAndreas Boehler
106*a1a3b679SAndreas Boehler        $subPromise = new self();
107*a1a3b679SAndreas Boehler        switch ($this->state) {
108*a1a3b679SAndreas Boehler            case self::PENDING :
109*a1a3b679SAndreas Boehler                $this->subscribers[] = [$subPromise, $onFulfilled, $onRejected];
110*a1a3b679SAndreas Boehler                break;
111*a1a3b679SAndreas Boehler            case self::FULFILLED :
112*a1a3b679SAndreas Boehler                $this->invokeCallback($subPromise, $onFulfilled);
113*a1a3b679SAndreas Boehler                break;
114*a1a3b679SAndreas Boehler            case self::REJECTED :
115*a1a3b679SAndreas Boehler                $this->invokeCallback($subPromise, $onRejected);
116*a1a3b679SAndreas Boehler                break;
117*a1a3b679SAndreas Boehler        }
118*a1a3b679SAndreas Boehler        return $subPromise;
119*a1a3b679SAndreas Boehler
120*a1a3b679SAndreas Boehler    }
121*a1a3b679SAndreas Boehler
122*a1a3b679SAndreas Boehler    /**
123*a1a3b679SAndreas Boehler     * Add a callback for when this promise is rejected.
124*a1a3b679SAndreas Boehler     *
125*a1a3b679SAndreas Boehler     * I would have used the word 'catch', but it's a reserved word in PHP, so
126*a1a3b679SAndreas Boehler     * we're not allowed to call our function that.
127*a1a3b679SAndreas Boehler     *
128*a1a3b679SAndreas Boehler     * @param callable $onRejected
129*a1a3b679SAndreas Boehler     * @return Promise
130*a1a3b679SAndreas Boehler     */
131*a1a3b679SAndreas Boehler    function error(callable $onRejected) {
132*a1a3b679SAndreas Boehler
133*a1a3b679SAndreas Boehler        return $this->then(null, $onRejected);
134*a1a3b679SAndreas Boehler
135*a1a3b679SAndreas Boehler    }
136*a1a3b679SAndreas Boehler
137*a1a3b679SAndreas Boehler    /**
138*a1a3b679SAndreas Boehler     * Marks this promise as fulfilled and sets its return value.
139*a1a3b679SAndreas Boehler     *
140*a1a3b679SAndreas Boehler     * @param mixed $value
141*a1a3b679SAndreas Boehler     * @return void
142*a1a3b679SAndreas Boehler     */
143*a1a3b679SAndreas Boehler    function fulfill($value = null) {
144*a1a3b679SAndreas Boehler        if ($this->state !== self::PENDING) {
145*a1a3b679SAndreas Boehler            throw new PromiseAlreadyResolvedException('This promise is already resolved, and you\'re not allowed to resolve a promise more than once');
146*a1a3b679SAndreas Boehler        }
147*a1a3b679SAndreas Boehler        $this->state = self::FULFILLED;
148*a1a3b679SAndreas Boehler        $this->value = $value;
149*a1a3b679SAndreas Boehler        foreach ($this->subscribers as $subscriber) {
150*a1a3b679SAndreas Boehler            $this->invokeCallback($subscriber[0], $subscriber[1]);
151*a1a3b679SAndreas Boehler        }
152*a1a3b679SAndreas Boehler    }
153*a1a3b679SAndreas Boehler
154*a1a3b679SAndreas Boehler    /**
155*a1a3b679SAndreas Boehler     * Marks this promise as rejected, and set it's rejection reason.
156*a1a3b679SAndreas Boehler     *
157*a1a3b679SAndreas Boehler     * @param mixed $reason
158*a1a3b679SAndreas Boehler     * @return void
159*a1a3b679SAndreas Boehler     */
160*a1a3b679SAndreas Boehler    function reject($reason = null) {
161*a1a3b679SAndreas Boehler        if ($this->state !== self::PENDING) {
162*a1a3b679SAndreas Boehler            throw new PromiseAlreadyResolvedException('This promise is already resolved, and you\'re not allowed to resolve a promise more than once');
163*a1a3b679SAndreas Boehler        }
164*a1a3b679SAndreas Boehler        $this->state = self::REJECTED;
165*a1a3b679SAndreas Boehler        $this->value = $reason;
166*a1a3b679SAndreas Boehler        foreach ($this->subscribers as $subscriber) {
167*a1a3b679SAndreas Boehler            $this->invokeCallback($subscriber[0], $subscriber[2]);
168*a1a3b679SAndreas Boehler        }
169*a1a3b679SAndreas Boehler
170*a1a3b679SAndreas Boehler    }
171*a1a3b679SAndreas Boehler
172*a1a3b679SAndreas Boehler    /**
173*a1a3b679SAndreas Boehler     * It's possible to send an array of promises to the all method. This
174*a1a3b679SAndreas Boehler     * method returns a promise that will be fulfilled, only if all the passed
175*a1a3b679SAndreas Boehler     * promises are fulfilled.
176*a1a3b679SAndreas Boehler     *
177*a1a3b679SAndreas Boehler     * @param Promise[] $promises
178*a1a3b679SAndreas Boehler     * @return Promise
179*a1a3b679SAndreas Boehler     */
180*a1a3b679SAndreas Boehler    static function all(array $promises) {
181*a1a3b679SAndreas Boehler
182*a1a3b679SAndreas Boehler        return new self(function($success, $fail) use ($promises) {
183*a1a3b679SAndreas Boehler
184*a1a3b679SAndreas Boehler            $successCount = 0;
185*a1a3b679SAndreas Boehler            $completeResult = [];
186*a1a3b679SAndreas Boehler
187*a1a3b679SAndreas Boehler            foreach ($promises as $promiseIndex => $subPromise) {
188*a1a3b679SAndreas Boehler
189*a1a3b679SAndreas Boehler                $subPromise->then(
190*a1a3b679SAndreas Boehler                    function($result) use ($promiseIndex, &$completeResult, &$successCount, $success, $promises) {
191*a1a3b679SAndreas Boehler                        $completeResult[$promiseIndex] = $result;
192*a1a3b679SAndreas Boehler                        $successCount++;
193*a1a3b679SAndreas Boehler                        if ($successCount === count($promises)) {
194*a1a3b679SAndreas Boehler                            $success($completeResult);
195*a1a3b679SAndreas Boehler                        }
196*a1a3b679SAndreas Boehler                        return $result;
197*a1a3b679SAndreas Boehler                    }
198*a1a3b679SAndreas Boehler                )->error(
199*a1a3b679SAndreas Boehler                    function($reason) use ($fail) {
200*a1a3b679SAndreas Boehler                        $fail($reason);
201*a1a3b679SAndreas Boehler                    }
202*a1a3b679SAndreas Boehler                );
203*a1a3b679SAndreas Boehler
204*a1a3b679SAndreas Boehler            }
205*a1a3b679SAndreas Boehler        });
206*a1a3b679SAndreas Boehler
207*a1a3b679SAndreas Boehler    }
208*a1a3b679SAndreas Boehler
209*a1a3b679SAndreas Boehler    /**
210*a1a3b679SAndreas Boehler     * This method is used to call either an onFulfilled or onRejected callback.
211*a1a3b679SAndreas Boehler     *
212*a1a3b679SAndreas Boehler     * This method makes sure that the result of these callbacks are handled
213*a1a3b679SAndreas Boehler     * correctly, and any chained promises are also correctly fulfilled or
214*a1a3b679SAndreas Boehler     * rejected.
215*a1a3b679SAndreas Boehler     *
216*a1a3b679SAndreas Boehler     * @param Promise $subPromise
217*a1a3b679SAndreas Boehler     * @param callable $callBack
218*a1a3b679SAndreas Boehler     * @return void
219*a1a3b679SAndreas Boehler     */
220*a1a3b679SAndreas Boehler    protected function invokeCallback(Promise $subPromise, callable $callBack = null) {
221*a1a3b679SAndreas Boehler
222*a1a3b679SAndreas Boehler        if (is_callable($callBack)) {
223*a1a3b679SAndreas Boehler            try {
224*a1a3b679SAndreas Boehler                $result = $callBack($this->value);
225*a1a3b679SAndreas Boehler                if ($result instanceof self) {
226*a1a3b679SAndreas Boehler                    $result->then([$subPromise, 'fulfill'], [$subPromise, 'reject']);
227*a1a3b679SAndreas Boehler                } else {
228*a1a3b679SAndreas Boehler                    $subPromise->fulfill($result);
229*a1a3b679SAndreas Boehler                }
230*a1a3b679SAndreas Boehler            } catch (Exception $e) {
231*a1a3b679SAndreas Boehler                $subPromise->reject($e);
232*a1a3b679SAndreas Boehler            }
233*a1a3b679SAndreas Boehler        } else {
234*a1a3b679SAndreas Boehler            if ($this->state === self::FULFILLED) {
235*a1a3b679SAndreas Boehler                $subPromise->fulfill($this->value);
236*a1a3b679SAndreas Boehler            } else {
237*a1a3b679SAndreas Boehler                $subPromise->reject($this->value);
238*a1a3b679SAndreas Boehler            }
239*a1a3b679SAndreas Boehler        }
240*a1a3b679SAndreas Boehler    }
241*a1a3b679SAndreas Boehler
242*a1a3b679SAndreas Boehler
243*a1a3b679SAndreas Boehler}
244