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