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