1<?php 2namespace GuzzleHttp\Ring\Future; 3 4use GuzzleHttp\Ring\Exception\CancelledFutureAccessException; 5use GuzzleHttp\Ring\Exception\RingException; 6use React\Promise\PromiseInterface; 7 8/** 9 * Implements common future functionality built on top of promises. 10 */ 11trait BaseFutureTrait 12{ 13 /** @var callable */ 14 private $waitfn; 15 16 /** @var callable */ 17 private $cancelfn; 18 19 /** @var PromiseInterface */ 20 private $wrappedPromise; 21 22 /** @var \Exception Error encountered. */ 23 private $error; 24 25 /** @var mixed Result of the future */ 26 private $result; 27 28 private $isRealized = false; 29 30 /** 31 * @param PromiseInterface $promise Promise to shadow with the future. 32 * @param callable $wait Function that blocks until the deferred 33 * computation has been resolved. This 34 * function MUST resolve the deferred value 35 * associated with the supplied promise. 36 * @param callable $cancel If possible and reasonable, provide a 37 * function that can be used to cancel the 38 * future from completing. 39 */ 40 public function __construct( 41 PromiseInterface $promise, 42 callable $wait = null, 43 callable $cancel = null 44 ) { 45 $this->wrappedPromise = $promise; 46 $this->waitfn = $wait; 47 $this->cancelfn = $cancel; 48 } 49 50 /** 51 * @return mixed 52 */ 53 public function wait() 54 { 55 if (!$this->isRealized) { 56 $this->addShadow(); 57 if (!$this->isRealized && $this->waitfn) { 58 $this->invokeWait(); 59 } 60 if (!$this->isRealized) { 61 $this->error = new RingException('Waiting did not resolve future'); 62 } 63 } 64 65 if ($this->error) { 66 throw $this->error; 67 } 68 69 return $this->result; 70 } 71 72 /** 73 * @return PromiseInterface 74 */ 75 public function promise() 76 { 77 return $this->wrappedPromise; 78 } 79 80 /** 81 * @return PromiseInterface 82 */ 83 public function then( 84 callable $onFulfilled = null, 85 callable $onRejected = null, 86 callable $onProgress = null 87 ) { 88 return $this->wrappedPromise->then($onFulfilled, $onRejected, $onProgress); 89 } 90 91 public function cancel() 92 { 93 if (!$this->isRealized) { 94 $cancelfn = $this->cancelfn; 95 $this->waitfn = $this->cancelfn = null; 96 $this->isRealized = true; 97 $this->error = new CancelledFutureAccessException(); 98 if ($cancelfn) { 99 $cancelfn($this); 100 } 101 } 102 } 103 104 private function addShadow() 105 { 106 // Get the result and error when the promise is resolved. Note that 107 // calling this function might trigger the resolution immediately. 108 $this->wrappedPromise->then( 109 function ($value) { 110 $this->isRealized = true; 111 $this->result = $value; 112 $this->waitfn = $this->cancelfn = null; 113 }, 114 function ($error) { 115 $this->isRealized = true; 116 $this->error = $error; 117 $this->waitfn = $this->cancelfn = null; 118 } 119 ); 120 } 121 122 private function invokeWait() 123 { 124 try { 125 $wait = $this->waitfn; 126 $this->waitfn = null; 127 $wait(); 128 } catch (\Exception $e) { 129 // Defer can throw to reject. 130 $this->error = $e; 131 $this->isRealized = true; 132 } 133 } 134} 135