Subversion Repositories php-qbpwcf

Rev

Details | Last modification | View Log | RSS feed

Rev Author Line No. Line
3 liveuser 1
<?php
2
 
3
namespace React\Promise;
4
 
5
class Promise implements ExtendedPromiseInterface, CancellablePromiseInterface
6
{
7
    private $canceller;
8
    private $result;
9
 
10
    private $handlers = [];
11
    private $progressHandlers = [];
12
 
13
    private $requiredCancelRequests = 0;
14
    private $cancelRequests = 0;
15
 
16
    public function __construct(callable $resolver, callable $canceller = null)
17
    {
18
        $this->canceller = $canceller;
19
 
20
        // Explicitly overwrite arguments with null values before invoking
21
        // resolver function. This ensure that these arguments do not show up
22
        // in the stack trace in PHP 7+ only.
23
        $cb = $resolver;
24
        $resolver = $canceller = null;
25
        $this->call($cb);
26
    }
27
 
28
    public function then(callable $onFulfilled = null, callable $onRejected = null, callable $onProgress = null)
29
    {
30
        if (null !== $this->result) {
31
            return $this->result->then($onFulfilled, $onRejected, $onProgress);
32
        }
33
 
34
        if (null === $this->canceller) {
35
            return new static($this->resolver($onFulfilled, $onRejected, $onProgress));
36
        }
37
 
38
        // This promise has a canceller, so we create a new child promise which
39
        // has a canceller that invokes the parent canceller if all other
40
        // followers are also cancelled. We keep a reference to this promise
41
        // instance for the static canceller function and clear this to avoid
42
        // keeping a cyclic reference between parent and follower.
43
        $parent = $this;
44
        ++$parent->requiredCancelRequests;
45
 
46
        return new static(
47
            $this->resolver($onFulfilled, $onRejected, $onProgress),
48
            static function () use (&$parent) {
49
                if (++$parent->cancelRequests >= $parent->requiredCancelRequests) {
50
                    $parent->cancel();
51
                }
52
 
53
                $parent = null;
54
            }
55
        );
56
    }
57
 
58
    public function done(callable $onFulfilled = null, callable $onRejected = null, callable $onProgress = null)
59
    {
60
        if (null !== $this->result) {
61
            return $this->result->done($onFulfilled, $onRejected, $onProgress);
62
        }
63
 
64
        $this->handlers[] = static function (ExtendedPromiseInterface $promise) use ($onFulfilled, $onRejected) {
65
            $promise
66
                ->done($onFulfilled, $onRejected);
67
        };
68
 
69
        if ($onProgress) {
70
            $this->progressHandlers[] = $onProgress;
71
        }
72
    }
73
 
74
    public function otherwise(callable $onRejected)
75
    {
76
        return $this->then(null, static function ($reason) use ($onRejected) {
77
            if (!_checkTypehint($onRejected, $reason)) {
78
                return new RejectedPromise($reason);
79
            }
80
 
81
            return $onRejected($reason);
82
        });
83
    }
84
 
85
    public function always(callable $onFulfilledOrRejected)
86
    {
87
        return $this->then(static function ($value) use ($onFulfilledOrRejected) {
88
            return resolve($onFulfilledOrRejected())->then(function () use ($value) {
89
                return $value;
90
            });
91
        }, static function ($reason) use ($onFulfilledOrRejected) {
92
            return resolve($onFulfilledOrRejected())->then(function () use ($reason) {
93
                return new RejectedPromise($reason);
94
            });
95
        });
96
    }
97
 
98
    public function progress(callable $onProgress)
99
    {
100
        return $this->then(null, null, $onProgress);
101
    }
102
 
103
    public function cancel()
104
    {
105
        if (null === $this->canceller || null !== $this->result) {
106
            return;
107
        }
108
 
109
        $canceller = $this->canceller;
110
        $this->canceller = null;
111
 
112
        $this->call($canceller);
113
    }
114
 
115
    private function resolver(callable $onFulfilled = null, callable $onRejected = null, callable $onProgress = null)
116
    {
117
        return function ($resolve, $reject, $notify) use ($onFulfilled, $onRejected, $onProgress) {
118
            if ($onProgress) {
119
                $progressHandler = static function ($update) use ($notify, $onProgress) {
120
                    try {
121
                        $notify($onProgress($update));
122
                    } catch (\Throwable $e) {
123
                        $notify($e);
124
                    } catch (\Exception $e) {
125
                        $notify($e);
126
                    }
127
                };
128
            } else {
129
                $progressHandler = $notify;
130
            }
131
 
132
            $this->handlers[] = static function (ExtendedPromiseInterface $promise) use ($onFulfilled, $onRejected, $resolve, $reject, $progressHandler) {
133
                $promise
134
                    ->then($onFulfilled, $onRejected)
135
                    ->done($resolve, $reject, $progressHandler);
136
            };
137
 
138
            $this->progressHandlers[] = $progressHandler;
139
        };
140
    }
141
 
142
    private function reject($reason = null)
143
    {
144
        if (null !== $this->result) {
145
            return;
146
        }
147
 
148
        $this->settle(reject($reason));
149
    }
150
 
151
    private function settle(ExtendedPromiseInterface $promise)
152
    {
153
        $promise = $this->unwrap($promise);
154
 
155
        if ($promise === $this) {
156
            $promise = new RejectedPromise(
157
                new \LogicException('Cannot resolve a promise with itself.')
158
            );
159
        }
160
 
161
        $handlers = $this->handlers;
162
 
163
        $this->progressHandlers = $this->handlers = [];
164
        $this->result = $promise;
165
        $this->canceller = null;
166
 
167
        foreach ($handlers as $handler) {
168
            $handler($promise);
169
        }
170
    }
171
 
172
    private function unwrap($promise)
173
    {
174
        $promise = $this->extract($promise);
175
 
176
        while ($promise instanceof self && null !== $promise->result) {
177
            $promise = $this->extract($promise->result);
178
        }
179
 
180
        return $promise;
181
    }
182
 
183
    private function extract($promise)
184
    {
185
        if ($promise instanceof LazyPromise) {
186
            $promise = $promise->promise();
187
        }
188
 
189
        return $promise;
190
    }
191
 
192
    private function call(callable $cb)
193
    {
194
        // Explicitly overwrite argument with null value. This ensure that this
195
        // argument does not show up in the stack trace in PHP 7+ only.
196
        $callback = $cb;
197
        $cb = null;
198
 
199
        // Use reflection to inspect number of arguments expected by this callback.
200
        // We did some careful benchmarking here: Using reflection to avoid unneeded
201
        // function arguments is actually faster than blindly passing them.
202
        // Also, this helps avoiding unnecessary function arguments in the call stack
203
        // if the callback creates an Exception (creating garbage cycles).
204
        if (\is_array($callback)) {
205
            $ref = new \ReflectionMethod($callback[0], $callback[1]);
206
        } elseif (\is_object($callback) && !$callback instanceof \Closure) {
207
            $ref = new \ReflectionMethod($callback, '__invoke');
208
        } else {
209
            $ref = new \ReflectionFunction($callback);
210
        }
211
        $args = $ref->getNumberOfParameters();
212
 
213
        try {
214
            if ($args === 0) {
215
                $callback();
216
            } else {
217
                // Keep references to this promise instance for the static resolve/reject functions.
218
                // By using static callbacks that are not bound to this instance
219
                // and passing the target promise instance by reference, we can
220
                // still execute its resolving logic and still clear this
221
                // reference when settling the promise. This helps avoiding
222
                // garbage cycles if any callback creates an Exception.
223
                // These assumptions are covered by the test suite, so if you ever feel like
224
                // refactoring this, go ahead, any alternative suggestions are welcome!
225
                $target =& $this;
226
                $progressHandlers =& $this->progressHandlers;
227
 
228
                $callback(
229
                    static function ($value = null) use (&$target) {
230
                        if ($target !== null) {
231
                            $target->settle(resolve($value));
232
                            $target = null;
233
                        }
234
                    },
235
                    static function ($reason = null) use (&$target) {
236
                        if ($target !== null) {
237
                            $target->reject($reason);
238
                            $target = null;
239
                        }
240
                    },
241
                    static function ($update = null) use (&$progressHandlers) {
242
                        foreach ($progressHandlers as $handler) {
243
                            $handler($update);
244
                        }
245
                    }
246
                );
247
            }
248
        } catch (\Throwable $e) {
249
            $target = null;
250
            $this->reject($e);
251
        } catch (\Exception $e) {
252
            $target = null;
253
            $this->reject($e);
254
        }
255
    }
256
}