Proyectos de Subversion Moodle

Rev

Rev 1 | | Comparar con el anterior | Ultima modificación | Ver Log |

Rev Autor Línea Nro. Línea
1 efrain 1
<?php
2
 
1441 ariadna 3
declare(strict_types=1);
4
 
1 efrain 5
namespace GuzzleHttp\Promise;
6
 
7
/**
8
 * Promises/A+ implementation that avoids recursion when possible.
9
 *
1441 ariadna 10
 * @see https://promisesaplus.com/
11
 *
12
 * @final
1 efrain 13
 */
14
class Promise implements PromiseInterface
15
{
16
    private $state = self::PENDING;
17
    private $result;
18
    private $cancelFn;
19
    private $waitFn;
20
    private $waitList;
21
    private $handlers = [];
22
 
23
    /**
24
     * @param callable $waitFn   Fn that when invoked resolves the promise.
25
     * @param callable $cancelFn Fn that when invoked cancels the promise.
26
     */
27
    public function __construct(
1441 ariadna 28
        ?callable $waitFn = null,
29
        ?callable $cancelFn = null
1 efrain 30
    ) {
31
        $this->waitFn = $waitFn;
32
        $this->cancelFn = $cancelFn;
33
    }
34
 
35
    public function then(
1441 ariadna 36
        ?callable $onFulfilled = null,
37
        ?callable $onRejected = null
38
    ): PromiseInterface {
1 efrain 39
        if ($this->state === self::PENDING) {
40
            $p = new Promise(null, [$this, 'cancel']);
41
            $this->handlers[] = [$p, $onFulfilled, $onRejected];
42
            $p->waitList = $this->waitList;
43
            $p->waitList[] = $this;
1441 ariadna 44
 
1 efrain 45
            return $p;
46
        }
47
 
48
        // Return a fulfilled promise and immediately invoke any callbacks.
49
        if ($this->state === self::FULFILLED) {
50
            $promise = Create::promiseFor($this->result);
1441 ariadna 51
 
1 efrain 52
            return $onFulfilled ? $promise->then($onFulfilled) : $promise;
53
        }
54
 
55
        // It's either cancelled or rejected, so return a rejected promise
56
        // and immediately invoke any callbacks.
57
        $rejection = Create::rejectionFor($this->result);
1441 ariadna 58
 
1 efrain 59
        return $onRejected ? $rejection->then(null, $onRejected) : $rejection;
60
    }
61
 
1441 ariadna 62
    public function otherwise(callable $onRejected): PromiseInterface
1 efrain 63
    {
64
        return $this->then(null, $onRejected);
65
    }
66
 
1441 ariadna 67
    public function wait(bool $unwrap = true)
1 efrain 68
    {
69
        $this->waitIfPending();
70
 
71
        if ($this->result instanceof PromiseInterface) {
72
            return $this->result->wait($unwrap);
73
        }
74
        if ($unwrap) {
75
            if ($this->state === self::FULFILLED) {
76
                return $this->result;
77
            }
78
            // It's rejected so "unwrap" and throw an exception.
79
            throw Create::exceptionFor($this->result);
80
        }
81
    }
82
 
1441 ariadna 83
    public function getState(): string
1 efrain 84
    {
85
        return $this->state;
86
    }
87
 
1441 ariadna 88
    public function cancel(): void
1 efrain 89
    {
90
        if ($this->state !== self::PENDING) {
91
            return;
92
        }
93
 
94
        $this->waitFn = $this->waitList = null;
95
 
96
        if ($this->cancelFn) {
97
            $fn = $this->cancelFn;
98
            $this->cancelFn = null;
99
            try {
100
                $fn();
101
            } catch (\Throwable $e) {
102
                $this->reject($e);
103
            }
104
        }
105
 
106
        // Reject the promise only if it wasn't rejected in a then callback.
107
        /** @psalm-suppress RedundantCondition */
108
        if ($this->state === self::PENDING) {
109
            $this->reject(new CancellationException('Promise has been cancelled'));
110
        }
111
    }
112
 
1441 ariadna 113
    public function resolve($value): void
1 efrain 114
    {
115
        $this->settle(self::FULFILLED, $value);
116
    }
117
 
1441 ariadna 118
    public function reject($reason): void
1 efrain 119
    {
120
        $this->settle(self::REJECTED, $reason);
121
    }
122
 
1441 ariadna 123
    private function settle(string $state, $value): void
1 efrain 124
    {
125
        if ($this->state !== self::PENDING) {
126
            // Ignore calls with the same resolution.
127
            if ($state === $this->state && $value === $this->result) {
128
                return;
129
            }
130
            throw $this->state === $state
131
                ? new \LogicException("The promise is already {$state}.")
132
                : new \LogicException("Cannot change a {$this->state} promise to {$state}");
133
        }
134
 
135
        if ($value === $this) {
136
            throw new \LogicException('Cannot fulfill or reject a promise with itself');
137
        }
138
 
139
        // Clear out the state of the promise but stash the handlers.
140
        $this->state = $state;
141
        $this->result = $value;
142
        $handlers = $this->handlers;
143
        $this->handlers = null;
144
        $this->waitList = $this->waitFn = null;
145
        $this->cancelFn = null;
146
 
147
        if (!$handlers) {
148
            return;
149
        }
150
 
151
        // If the value was not a settled promise or a thenable, then resolve
152
        // it in the task queue using the correct ID.
153
        if (!is_object($value) || !method_exists($value, 'then')) {
154
            $id = $state === self::FULFILLED ? 1 : 2;
155
            // It's a success, so resolve the handlers in the queue.
1441 ariadna 156
            Utils::queue()->add(static function () use ($id, $value, $handlers): void {
1 efrain 157
                foreach ($handlers as $handler) {
158
                    self::callHandler($id, $value, $handler);
159
                }
160
            });
161
        } elseif ($value instanceof Promise && Is::pending($value)) {
162
            // We can just merge our handlers onto the next promise.
163
            $value->handlers = array_merge($value->handlers, $handlers);
164
        } else {
165
            // Resolve the handlers when the forwarded promise is resolved.
166
            $value->then(
1441 ariadna 167
                static function ($value) use ($handlers): void {
1 efrain 168
                    foreach ($handlers as $handler) {
169
                        self::callHandler(1, $value, $handler);
170
                    }
171
                },
1441 ariadna 172
                static function ($reason) use ($handlers): void {
1 efrain 173
                    foreach ($handlers as $handler) {
174
                        self::callHandler(2, $reason, $handler);
175
                    }
176
                }
177
            );
178
        }
179
    }
180
 
181
    /**
182
     * Call a stack of handlers using a specific callback index and value.
183
     *
184
     * @param int   $index   1 (resolve) or 2 (reject).
185
     * @param mixed $value   Value to pass to the callback.
186
     * @param array $handler Array of handler data (promise and callbacks).
187
     */
1441 ariadna 188
    private static function callHandler(int $index, $value, array $handler): void
1 efrain 189
    {
190
        /** @var PromiseInterface $promise */
191
        $promise = $handler[0];
192
 
193
        // The promise may have been cancelled or resolved before placing
194
        // this thunk in the queue.
195
        if (Is::settled($promise)) {
196
            return;
197
        }
198
 
199
        try {
200
            if (isset($handler[$index])) {
201
                /*
202
                 * If $f throws an exception, then $handler will be in the exception
203
                 * stack trace. Since $handler contains a reference to the callable
204
                 * itself we get a circular reference. We clear the $handler
205
                 * here to avoid that memory leak.
206
                 */
207
                $f = $handler[$index];
208
                unset($handler);
209
                $promise->resolve($f($value));
210
            } elseif ($index === 1) {
211
                // Forward resolution values as-is.
212
                $promise->resolve($value);
213
            } else {
214
                // Forward rejections down the chain.
215
                $promise->reject($value);
216
            }
217
        } catch (\Throwable $reason) {
218
            $promise->reject($reason);
219
        }
220
    }
221
 
1441 ariadna 222
    private function waitIfPending(): void
1 efrain 223
    {
224
        if ($this->state !== self::PENDING) {
225
            return;
226
        } elseif ($this->waitFn) {
227
            $this->invokeWaitFn();
228
        } elseif ($this->waitList) {
229
            $this->invokeWaitList();
230
        } else {
231
            // If there's no wait function, then reject the promise.
232
            $this->reject('Cannot wait on a promise that has '
1441 ariadna 233
                .'no internal wait function. You must provide a wait '
234
                .'function when constructing the promise to be able to '
235
                .'wait on a promise.');
1 efrain 236
        }
237
 
238
        Utils::queue()->run();
239
 
240
        /** @psalm-suppress RedundantCondition */
241
        if ($this->state === self::PENDING) {
242
            $this->reject('Invoking the wait callback did not resolve the promise');
243
        }
244
    }
245
 
1441 ariadna 246
    private function invokeWaitFn(): void
1 efrain 247
    {
248
        try {
249
            $wfn = $this->waitFn;
250
            $this->waitFn = null;
251
            $wfn(true);
1441 ariadna 252
        } catch (\Throwable $reason) {
1 efrain 253
            if ($this->state === self::PENDING) {
254
                // The promise has not been resolved yet, so reject the promise
255
                // with the exception.
256
                $this->reject($reason);
257
            } else {
258
                // The promise was already resolved, so there's a problem in
259
                // the application.
260
                throw $reason;
261
            }
262
        }
263
    }
264
 
1441 ariadna 265
    private function invokeWaitList(): void
1 efrain 266
    {
267
        $waitList = $this->waitList;
268
        $this->waitList = null;
269
 
270
        foreach ($waitList as $result) {
271
            do {
272
                $result->waitIfPending();
273
                $result = $result->result;
274
            } while ($result instanceof Promise);
275
 
276
            if ($result instanceof PromiseInterface) {
277
                $result->wait(false);
278
            }
279
        }
280
    }
281
}