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