1<?php
2
3namespace GuzzleHttp\Promise;
4
5final class Utils
6{
7    /**
8     * Get the global task queue used for promise resolution.
9     *
10     * This task queue MUST be run in an event loop in order for promises to be
11     * settled asynchronously. It will be automatically run when synchronously
12     * waiting on a promise.
13     *
14     * <code>
15     * while ($eventLoop->isRunning()) {
16     *     GuzzleHttp\Promise\Utils::queue()->run();
17     * }
18     * </code>
19     *
20     * @param TaskQueueInterface $assign Optionally specify a new queue instance.
21     *
22     * @return TaskQueueInterface
23     */
24    public static function queue(TaskQueueInterface $assign = null)
25    {
26        static $queue;
27
28        if ($assign) {
29            $queue = $assign;
30        } elseif (!$queue) {
31            $queue = new TaskQueue();
32        }
33
34        return $queue;
35    }
36
37    /**
38     * Adds a function to run in the task queue when it is next `run()` and
39     * returns a promise that is fulfilled or rejected with the result.
40     *
41     * @param callable $task Task function to run.
42     *
43     * @return PromiseInterface
44     */
45    public static function task(callable $task)
46    {
47        $queue = self::queue();
48        $promise = new Promise([$queue, 'run']);
49        $queue->add(function () use ($task, $promise) {
50            try {
51                $promise->resolve($task());
52            } catch (\Throwable $e) {
53                $promise->reject($e);
54            } catch (\Exception $e) {
55                $promise->reject($e);
56            }
57        });
58
59        return $promise;
60    }
61
62    /**
63     * Synchronously waits on a promise to resolve and returns an inspection
64     * state array.
65     *
66     * Returns a state associative array containing a "state" key mapping to a
67     * valid promise state. If the state of the promise is "fulfilled", the
68     * array will contain a "value" key mapping to the fulfilled value of the
69     * promise. If the promise is rejected, the array will contain a "reason"
70     * key mapping to the rejection reason of the promise.
71     *
72     * @param PromiseInterface $promise Promise or value.
73     *
74     * @return array
75     */
76    public static function inspect(PromiseInterface $promise)
77    {
78        try {
79            return [
80                'state' => PromiseInterface::FULFILLED,
81                'value' => $promise->wait()
82            ];
83        } catch (RejectionException $e) {
84            return ['state' => PromiseInterface::REJECTED, 'reason' => $e->getReason()];
85        } catch (\Throwable $e) {
86            return ['state' => PromiseInterface::REJECTED, 'reason' => $e];
87        } catch (\Exception $e) {
88            return ['state' => PromiseInterface::REJECTED, 'reason' => $e];
89        }
90    }
91
92    /**
93     * Waits on all of the provided promises, but does not unwrap rejected
94     * promises as thrown exception.
95     *
96     * Returns an array of inspection state arrays.
97     *
98     * @see inspect for the inspection state array format.
99     *
100     * @param PromiseInterface[] $promises Traversable of promises to wait upon.
101     *
102     * @return array
103     */
104    public static function inspectAll($promises)
105    {
106        $results = [];
107        foreach ($promises as $key => $promise) {
108            $results[$key] = inspect($promise);
109        }
110
111        return $results;
112    }
113
114    /**
115     * Waits on all of the provided promises and returns the fulfilled values.
116     *
117     * Returns an array that contains the value of each promise (in the same
118     * order the promises were provided). An exception is thrown if any of the
119     * promises are rejected.
120     *
121     * @param iterable<PromiseInterface> $promises Iterable of PromiseInterface objects to wait on.
122     *
123     * @return array
124     *
125     * @throws \Exception on error
126     * @throws \Throwable on error in PHP >=7
127     */
128    public static function unwrap($promises)
129    {
130        $results = [];
131        foreach ($promises as $key => $promise) {
132            $results[$key] = $promise->wait();
133        }
134
135        return $results;
136    }
137
138    /**
139     * Given an array of promises, return a promise that is fulfilled when all
140     * the items in the array are fulfilled.
141     *
142     * The promise's fulfillment value is an array with fulfillment values at
143     * respective positions to the original array. If any promise in the array
144     * rejects, the returned promise is rejected with the rejection reason.
145     *
146     * @param mixed $promises  Promises or values.
147     * @param bool  $recursive If true, resolves new promises that might have been added to the stack during its own resolution.
148     *
149     * @return PromiseInterface
150     */
151    public static function all($promises, $recursive = false)
152    {
153        $results = [];
154        $promise = Each::of(
155            $promises,
156            function ($value, $idx) use (&$results) {
157                $results[$idx] = $value;
158            },
159            function ($reason, $idx, Promise $aggregate) {
160                $aggregate->reject($reason);
161            }
162        )->then(function () use (&$results) {
163            ksort($results);
164            return $results;
165        });
166
167        if (true === $recursive) {
168            $promise = $promise->then(function ($results) use ($recursive, &$promises) {
169                foreach ($promises as $promise) {
170                    if (Is::pending($promise)) {
171                        return self::all($promises, $recursive);
172                    }
173                }
174                return $results;
175            });
176        }
177
178        return $promise;
179    }
180
181    /**
182     * Initiate a competitive race between multiple promises or values (values
183     * will become immediately fulfilled promises).
184     *
185     * When count amount of promises have been fulfilled, the returned promise
186     * is fulfilled with an array that contains the fulfillment values of the
187     * winners in order of resolution.
188     *
189     * This promise is rejected with a {@see AggregateException} if the number
190     * of fulfilled promises is less than the desired $count.
191     *
192     * @param int   $count    Total number of promises.
193     * @param mixed $promises Promises or values.
194     *
195     * @return PromiseInterface
196     */
197    public static function some($count, $promises)
198    {
199        $results = [];
200        $rejections = [];
201
202        return Each::of(
203            $promises,
204            function ($value, $idx, PromiseInterface $p) use (&$results, $count) {
205                if (Is::settled($p)) {
206                    return;
207                }
208                $results[$idx] = $value;
209                if (count($results) >= $count) {
210                    $p->resolve(null);
211                }
212            },
213            function ($reason) use (&$rejections) {
214                $rejections[] = $reason;
215            }
216        )->then(
217            function () use (&$results, &$rejections, $count) {
218                if (count($results) !== $count) {
219                    throw new AggregateException(
220                        'Not enough promises to fulfill count',
221                        $rejections
222                    );
223                }
224                ksort($results);
225                return array_values($results);
226            }
227        );
228    }
229
230    /**
231     * Like some(), with 1 as count. However, if the promise fulfills, the
232     * fulfillment value is not an array of 1 but the value directly.
233     *
234     * @param mixed $promises Promises or values.
235     *
236     * @return PromiseInterface
237     */
238    public static function any($promises)
239    {
240        return self::some(1, $promises)->then(function ($values) {
241            return $values[0];
242        });
243    }
244
245    /**
246     * Returns a promise that is fulfilled when all of the provided promises have
247     * been fulfilled or rejected.
248     *
249     * The returned promise is fulfilled with an array of inspection state arrays.
250     *
251     * @see inspect for the inspection state array format.
252     *
253     * @param mixed $promises Promises or values.
254     *
255     * @return PromiseInterface
256     */
257    public static function settle($promises)
258    {
259        $results = [];
260
261        return Each::of(
262            $promises,
263            function ($value, $idx) use (&$results) {
264                $results[$idx] = ['state' => PromiseInterface::FULFILLED, 'value' => $value];
265            },
266            function ($reason, $idx) use (&$results) {
267                $results[$idx] = ['state' => PromiseInterface::REJECTED, 'reason' => $reason];
268            }
269        )->then(function () use (&$results) {
270            ksort($results);
271            return $results;
272        });
273    }
274}
275