1*a1a3b679SAndreas Boehler<?php 2*a1a3b679SAndreas Boehler 3*a1a3b679SAndreas Boehlernamespace Sabre\Event; 4*a1a3b679SAndreas Boehler 5*a1a3b679SAndreas Boehleruse Exception; 6*a1a3b679SAndreas Boehler 7*a1a3b679SAndreas Boehler/** 8*a1a3b679SAndreas Boehler * An implementation of the Promise pattern. 9*a1a3b679SAndreas Boehler * 10*a1a3b679SAndreas Boehler * Promises basically allow you to avoid what is commonly called 'callback 11*a1a3b679SAndreas Boehler * hell'. It allows for easily chaining of asynchronous operations. 12*a1a3b679SAndreas Boehler * 13*a1a3b679SAndreas Boehler * @copyright Copyright (C) 2013-2015 fruux GmbH (https://fruux.com/). 14*a1a3b679SAndreas Boehler * @author Evert Pot (http://evertpot.com/) 15*a1a3b679SAndreas Boehler * @license http://sabre.io/license/ Modified BSD License 16*a1a3b679SAndreas Boehler */ 17*a1a3b679SAndreas Boehlerclass Promise { 18*a1a3b679SAndreas Boehler 19*a1a3b679SAndreas Boehler /** 20*a1a3b679SAndreas Boehler * Pending promise. No result yet. 21*a1a3b679SAndreas Boehler */ 22*a1a3b679SAndreas Boehler const PENDING = 0; 23*a1a3b679SAndreas Boehler 24*a1a3b679SAndreas Boehler /** 25*a1a3b679SAndreas Boehler * The promise has been fulfilled. It was successful. 26*a1a3b679SAndreas Boehler */ 27*a1a3b679SAndreas Boehler const FULFILLED = 1; 28*a1a3b679SAndreas Boehler 29*a1a3b679SAndreas Boehler /** 30*a1a3b679SAndreas Boehler * The promise was rejected. The operation failed. 31*a1a3b679SAndreas Boehler */ 32*a1a3b679SAndreas Boehler const REJECTED = 2; 33*a1a3b679SAndreas Boehler 34*a1a3b679SAndreas Boehler /** 35*a1a3b679SAndreas Boehler * The current state of this promise. 36*a1a3b679SAndreas Boehler * 37*a1a3b679SAndreas Boehler * @var int 38*a1a3b679SAndreas Boehler */ 39*a1a3b679SAndreas Boehler protected $state = self::PENDING; 40*a1a3b679SAndreas Boehler 41*a1a3b679SAndreas Boehler /** 42*a1a3b679SAndreas Boehler * A list of subscribers. Subscribers are the callbacks that want us to let 43*a1a3b679SAndreas Boehler * them know if the callback was fulfilled or rejected. 44*a1a3b679SAndreas Boehler * 45*a1a3b679SAndreas Boehler * @var array 46*a1a3b679SAndreas Boehler */ 47*a1a3b679SAndreas Boehler protected $subscribers = []; 48*a1a3b679SAndreas Boehler 49*a1a3b679SAndreas Boehler /** 50*a1a3b679SAndreas Boehler * The result of the promise. 51*a1a3b679SAndreas Boehler * 52*a1a3b679SAndreas Boehler * If the promise was fulfilled, this will be the result value. If the 53*a1a3b679SAndreas Boehler * promise was rejected, this is most commonly an exception. 54*a1a3b679SAndreas Boehler * 55*a1a3b679SAndreas Boehler * @var mixed 56*a1a3b679SAndreas Boehler */ 57*a1a3b679SAndreas Boehler protected $value = null; 58*a1a3b679SAndreas Boehler 59*a1a3b679SAndreas Boehler /** 60*a1a3b679SAndreas Boehler * Creates the promise. 61*a1a3b679SAndreas Boehler * 62*a1a3b679SAndreas Boehler * The passed argument is the executor. The executor is automatically 63*a1a3b679SAndreas Boehler * called with two arguments. 64*a1a3b679SAndreas Boehler * 65*a1a3b679SAndreas Boehler * Each are callbacks that map to $this->fulfill and $this->reject. 66*a1a3b679SAndreas Boehler * Using the executor is optional. 67*a1a3b679SAndreas Boehler * 68*a1a3b679SAndreas Boehler * @param callable $executor 69*a1a3b679SAndreas Boehler */ 70*a1a3b679SAndreas Boehler function __construct(callable $executor = null) { 71*a1a3b679SAndreas Boehler 72*a1a3b679SAndreas Boehler if ($executor) { 73*a1a3b679SAndreas Boehler $executor( 74*a1a3b679SAndreas Boehler [$this, 'fulfill'], 75*a1a3b679SAndreas Boehler [$this, 'reject'] 76*a1a3b679SAndreas Boehler ); 77*a1a3b679SAndreas Boehler } 78*a1a3b679SAndreas Boehler 79*a1a3b679SAndreas Boehler } 80*a1a3b679SAndreas Boehler 81*a1a3b679SAndreas Boehler /** 82*a1a3b679SAndreas Boehler * This method allows you to specify the callback that will be called after 83*a1a3b679SAndreas Boehler * the promise has been fulfilled or rejected. 84*a1a3b679SAndreas Boehler * 85*a1a3b679SAndreas Boehler * Both arguments are optional. 86*a1a3b679SAndreas Boehler * 87*a1a3b679SAndreas Boehler * This method returns a new promise, which can be used for chaining. 88*a1a3b679SAndreas Boehler * If either the onFulfilled or onRejected callback is called, you may 89*a1a3b679SAndreas Boehler * return a result from this callback. 90*a1a3b679SAndreas Boehler * 91*a1a3b679SAndreas Boehler * If the result of this callback is yet another promise, the result of 92*a1a3b679SAndreas Boehler * _that_ promise will be used to set the result of the returned promise. 93*a1a3b679SAndreas Boehler * 94*a1a3b679SAndreas Boehler * If either of the callbacks return any other value, the returned promise 95*a1a3b679SAndreas Boehler * is automatically fulfilled with that value. 96*a1a3b679SAndreas Boehler * 97*a1a3b679SAndreas Boehler * If either of the callbacks throw an exception, the returned promise will 98*a1a3b679SAndreas Boehler * be rejected and the exception will be passed back. 99*a1a3b679SAndreas Boehler * 100*a1a3b679SAndreas Boehler * @param callable $onFulfilled 101*a1a3b679SAndreas Boehler * @param callable $onRejected 102*a1a3b679SAndreas Boehler * @return Promise 103*a1a3b679SAndreas Boehler */ 104*a1a3b679SAndreas Boehler function then(callable $onFulfilled = null, callable $onRejected = null) { 105*a1a3b679SAndreas Boehler 106*a1a3b679SAndreas Boehler $subPromise = new self(); 107*a1a3b679SAndreas Boehler switch ($this->state) { 108*a1a3b679SAndreas Boehler case self::PENDING : 109*a1a3b679SAndreas Boehler $this->subscribers[] = [$subPromise, $onFulfilled, $onRejected]; 110*a1a3b679SAndreas Boehler break; 111*a1a3b679SAndreas Boehler case self::FULFILLED : 112*a1a3b679SAndreas Boehler $this->invokeCallback($subPromise, $onFulfilled); 113*a1a3b679SAndreas Boehler break; 114*a1a3b679SAndreas Boehler case self::REJECTED : 115*a1a3b679SAndreas Boehler $this->invokeCallback($subPromise, $onRejected); 116*a1a3b679SAndreas Boehler break; 117*a1a3b679SAndreas Boehler } 118*a1a3b679SAndreas Boehler return $subPromise; 119*a1a3b679SAndreas Boehler 120*a1a3b679SAndreas Boehler } 121*a1a3b679SAndreas Boehler 122*a1a3b679SAndreas Boehler /** 123*a1a3b679SAndreas Boehler * Add a callback for when this promise is rejected. 124*a1a3b679SAndreas Boehler * 125*a1a3b679SAndreas Boehler * I would have used the word 'catch', but it's a reserved word in PHP, so 126*a1a3b679SAndreas Boehler * we're not allowed to call our function that. 127*a1a3b679SAndreas Boehler * 128*a1a3b679SAndreas Boehler * @param callable $onRejected 129*a1a3b679SAndreas Boehler * @return Promise 130*a1a3b679SAndreas Boehler */ 131*a1a3b679SAndreas Boehler function error(callable $onRejected) { 132*a1a3b679SAndreas Boehler 133*a1a3b679SAndreas Boehler return $this->then(null, $onRejected); 134*a1a3b679SAndreas Boehler 135*a1a3b679SAndreas Boehler } 136*a1a3b679SAndreas Boehler 137*a1a3b679SAndreas Boehler /** 138*a1a3b679SAndreas Boehler * Marks this promise as fulfilled and sets its return value. 139*a1a3b679SAndreas Boehler * 140*a1a3b679SAndreas Boehler * @param mixed $value 141*a1a3b679SAndreas Boehler * @return void 142*a1a3b679SAndreas Boehler */ 143*a1a3b679SAndreas Boehler function fulfill($value = null) { 144*a1a3b679SAndreas Boehler if ($this->state !== self::PENDING) { 145*a1a3b679SAndreas Boehler throw new PromiseAlreadyResolvedException('This promise is already resolved, and you\'re not allowed to resolve a promise more than once'); 146*a1a3b679SAndreas Boehler } 147*a1a3b679SAndreas Boehler $this->state = self::FULFILLED; 148*a1a3b679SAndreas Boehler $this->value = $value; 149*a1a3b679SAndreas Boehler foreach ($this->subscribers as $subscriber) { 150*a1a3b679SAndreas Boehler $this->invokeCallback($subscriber[0], $subscriber[1]); 151*a1a3b679SAndreas Boehler } 152*a1a3b679SAndreas Boehler } 153*a1a3b679SAndreas Boehler 154*a1a3b679SAndreas Boehler /** 155*a1a3b679SAndreas Boehler * Marks this promise as rejected, and set it's rejection reason. 156*a1a3b679SAndreas Boehler * 157*a1a3b679SAndreas Boehler * @param mixed $reason 158*a1a3b679SAndreas Boehler * @return void 159*a1a3b679SAndreas Boehler */ 160*a1a3b679SAndreas Boehler function reject($reason = null) { 161*a1a3b679SAndreas Boehler if ($this->state !== self::PENDING) { 162*a1a3b679SAndreas Boehler throw new PromiseAlreadyResolvedException('This promise is already resolved, and you\'re not allowed to resolve a promise more than once'); 163*a1a3b679SAndreas Boehler } 164*a1a3b679SAndreas Boehler $this->state = self::REJECTED; 165*a1a3b679SAndreas Boehler $this->value = $reason; 166*a1a3b679SAndreas Boehler foreach ($this->subscribers as $subscriber) { 167*a1a3b679SAndreas Boehler $this->invokeCallback($subscriber[0], $subscriber[2]); 168*a1a3b679SAndreas Boehler } 169*a1a3b679SAndreas Boehler 170*a1a3b679SAndreas Boehler } 171*a1a3b679SAndreas Boehler 172*a1a3b679SAndreas Boehler /** 173*a1a3b679SAndreas Boehler * It's possible to send an array of promises to the all method. This 174*a1a3b679SAndreas Boehler * method returns a promise that will be fulfilled, only if all the passed 175*a1a3b679SAndreas Boehler * promises are fulfilled. 176*a1a3b679SAndreas Boehler * 177*a1a3b679SAndreas Boehler * @param Promise[] $promises 178*a1a3b679SAndreas Boehler * @return Promise 179*a1a3b679SAndreas Boehler */ 180*a1a3b679SAndreas Boehler static function all(array $promises) { 181*a1a3b679SAndreas Boehler 182*a1a3b679SAndreas Boehler return new self(function($success, $fail) use ($promises) { 183*a1a3b679SAndreas Boehler 184*a1a3b679SAndreas Boehler $successCount = 0; 185*a1a3b679SAndreas Boehler $completeResult = []; 186*a1a3b679SAndreas Boehler 187*a1a3b679SAndreas Boehler foreach ($promises as $promiseIndex => $subPromise) { 188*a1a3b679SAndreas Boehler 189*a1a3b679SAndreas Boehler $subPromise->then( 190*a1a3b679SAndreas Boehler function($result) use ($promiseIndex, &$completeResult, &$successCount, $success, $promises) { 191*a1a3b679SAndreas Boehler $completeResult[$promiseIndex] = $result; 192*a1a3b679SAndreas Boehler $successCount++; 193*a1a3b679SAndreas Boehler if ($successCount === count($promises)) { 194*a1a3b679SAndreas Boehler $success($completeResult); 195*a1a3b679SAndreas Boehler } 196*a1a3b679SAndreas Boehler return $result; 197*a1a3b679SAndreas Boehler } 198*a1a3b679SAndreas Boehler )->error( 199*a1a3b679SAndreas Boehler function($reason) use ($fail) { 200*a1a3b679SAndreas Boehler $fail($reason); 201*a1a3b679SAndreas Boehler } 202*a1a3b679SAndreas Boehler ); 203*a1a3b679SAndreas Boehler 204*a1a3b679SAndreas Boehler } 205*a1a3b679SAndreas Boehler }); 206*a1a3b679SAndreas Boehler 207*a1a3b679SAndreas Boehler } 208*a1a3b679SAndreas Boehler 209*a1a3b679SAndreas Boehler /** 210*a1a3b679SAndreas Boehler * This method is used to call either an onFulfilled or onRejected callback. 211*a1a3b679SAndreas Boehler * 212*a1a3b679SAndreas Boehler * This method makes sure that the result of these callbacks are handled 213*a1a3b679SAndreas Boehler * correctly, and any chained promises are also correctly fulfilled or 214*a1a3b679SAndreas Boehler * rejected. 215*a1a3b679SAndreas Boehler * 216*a1a3b679SAndreas Boehler * @param Promise $subPromise 217*a1a3b679SAndreas Boehler * @param callable $callBack 218*a1a3b679SAndreas Boehler * @return void 219*a1a3b679SAndreas Boehler */ 220*a1a3b679SAndreas Boehler protected function invokeCallback(Promise $subPromise, callable $callBack = null) { 221*a1a3b679SAndreas Boehler 222*a1a3b679SAndreas Boehler if (is_callable($callBack)) { 223*a1a3b679SAndreas Boehler try { 224*a1a3b679SAndreas Boehler $result = $callBack($this->value); 225*a1a3b679SAndreas Boehler if ($result instanceof self) { 226*a1a3b679SAndreas Boehler $result->then([$subPromise, 'fulfill'], [$subPromise, 'reject']); 227*a1a3b679SAndreas Boehler } else { 228*a1a3b679SAndreas Boehler $subPromise->fulfill($result); 229*a1a3b679SAndreas Boehler } 230*a1a3b679SAndreas Boehler } catch (Exception $e) { 231*a1a3b679SAndreas Boehler $subPromise->reject($e); 232*a1a3b679SAndreas Boehler } 233*a1a3b679SAndreas Boehler } else { 234*a1a3b679SAndreas Boehler if ($this->state === self::FULFILLED) { 235*a1a3b679SAndreas Boehler $subPromise->fulfill($this->value); 236*a1a3b679SAndreas Boehler } else { 237*a1a3b679SAndreas Boehler $subPromise->reject($this->value); 238*a1a3b679SAndreas Boehler } 239*a1a3b679SAndreas Boehler } 240*a1a3b679SAndreas Boehler } 241*a1a3b679SAndreas Boehler 242*a1a3b679SAndreas Boehler 243*a1a3b679SAndreas Boehler} 244