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