-
-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
8 changed files
with
473 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,74 @@ | ||
<?php | ||
namespace Gt\Promise; | ||
|
||
use Throwable; | ||
|
||
class Deferred implements DeferredInterface { | ||
private Promise $promise; | ||
/** @var callable */ | ||
private $resolveCallback; | ||
/** @var callable */ | ||
private $rejectCallback; | ||
/** @var callable[] */ | ||
private array $processList; | ||
/** @var callable[] */ | ||
private array $completeCallback; | ||
private bool $activated; | ||
|
||
public function __construct(callable $process = null) { | ||
$this->promise = new Promise(function($resolve, $reject):void { | ||
$this->resolveCallback = $resolve; | ||
$this->rejectCallback = $reject; | ||
}); | ||
|
||
$this->processList = []; | ||
$this->completeCallback = []; | ||
$this->activated = true; | ||
|
||
if(!is_null($process)) { | ||
$this->addProcess($process); | ||
} | ||
} | ||
|
||
public function getPromise():PromiseInterface { | ||
return $this->promise; | ||
} | ||
|
||
public function resolve($value = null):void { | ||
$this->complete(); | ||
call_user_func($this->resolveCallback, $value); | ||
} | ||
|
||
public function reject(Throwable $reason):void { | ||
$this->complete(); | ||
call_user_func($this->rejectCallback, $reason); | ||
} | ||
|
||
private function complete():void { | ||
if(!$this->activated) { | ||
return; | ||
} | ||
|
||
$this->activated = false; | ||
foreach($this->completeCallback as $callback) { | ||
call_user_func($callback); | ||
} | ||
} | ||
|
||
public function addProcess(callable $process):void { | ||
array_push($this->processList, $process); | ||
} | ||
|
||
/** @return callable[] */ | ||
public function getProcessList():array { | ||
return $this->processList; | ||
} | ||
|
||
public function isActive():bool { | ||
return $this->activated; | ||
} | ||
|
||
public function addCompleteCallback(callable $callback):void { | ||
array_push($this->completeCallback, $callback); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,74 @@ | ||
<?php | ||
namespace Gt\Promise; | ||
|
||
use Throwable; | ||
|
||
class FulfilledPromise implements PromiseInterface { | ||
use Resolvable; | ||
|
||
/** @var mixed */ | ||
private $value; | ||
|
||
/** @param ?mixed $promiseOrValue */ | ||
public function __construct($promiseOrValue = null) { | ||
if($promiseOrValue instanceof PromiseInterface) { | ||
throw new PromiseException("A FulfilledPromise must be resolved with a concrete value, not a Promise."); | ||
} | ||
|
||
$this->value = $promiseOrValue; | ||
} | ||
|
||
public function then( | ||
callable $onFulfilled = null, | ||
callable $onRejected = null | ||
):PromiseInterface { | ||
if(is_null($onFulfilled)) { | ||
return $this; | ||
} | ||
|
||
return new Promise( | ||
function(callable $resolve, callable $reject) | ||
use ($onFulfilled):void { | ||
try { | ||
$resolve($onFulfilled($this->value)); | ||
} | ||
catch(Throwable $reason) { | ||
$reject($reason); | ||
} | ||
} | ||
); | ||
} | ||
|
||
public function complete( | ||
callable $onFulfilled = null, | ||
callable $onRejected = null | ||
):void { | ||
if(is_null($onFulfilled)) { | ||
return; | ||
} | ||
|
||
$result = $onFulfilled($this->value); | ||
|
||
if($result instanceof PromiseInterface) { | ||
$result->complete(); | ||
} | ||
} | ||
|
||
public function catch(callable $onRejected):PromiseInterface { | ||
return $this; | ||
} | ||
|
||
public function finally( | ||
callable $onFulfilledOrRejected | ||
):PromiseInterface { | ||
return $this->then( | ||
function($value) | ||
use ($onFulfilledOrRejected):PromiseInterface { | ||
return $this->resolve($onFulfilledOrRejected()) | ||
->then(function() use ($value) { | ||
return $value; | ||
}); | ||
} | ||
); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,148 @@ | ||
<?php | ||
namespace Gt\Promise; | ||
|
||
use Throwable; | ||
|
||
class Promise implements PromiseInterface { | ||
use Resolvable; | ||
|
||
/** @var mixed */ | ||
private $result; | ||
/** @var callable[] */ | ||
private array $handlers; | ||
|
||
/** | ||
* @param callable $executor A function to be executed by the | ||
* constructor, during the process of constructing the Promise. The | ||
* executor is custom code that ties an outcome to a promise. You, | ||
* the programmer, write the executor. The signature of this function is | ||
* expected to be: | ||
* function(callable $resolutionFunc, callable $rejectionFunc):void {} | ||
*/ | ||
public function __construct(callable $executor) { | ||
$this->handlers = []; | ||
$this->call($executor); | ||
} | ||
|
||
public function then( | ||
callable $onFulfilled = null, | ||
callable $onRejected = null | ||
):PromiseInterface { | ||
if(!is_null($this->result)) { | ||
return $this->result->then( | ||
$onFulfilled, | ||
$onRejected | ||
); | ||
} | ||
|
||
return new static($this->resolver($onFulfilled, $onRejected)); | ||
} | ||
|
||
public function catch( | ||
callable $onRejected | ||
):PromiseInterface { | ||
return $this->then( | ||
null, | ||
fn(Throwable $reason):PromiseInterface => $onRejected($reason) | ||
); | ||
} | ||
|
||
public function finally( | ||
callable $onFulfilledOrRejected | ||
):PromiseInterface { | ||
return $this->then( | ||
fn($value) => $this->resolve($onFulfilledOrRejected())->then(fn() => $value), | ||
fn(Throwable $reason) => $this->resolve($onFulfilledOrRejected())->then(fn() => new RejectedPromise($reason)) | ||
); | ||
} | ||
|
||
public function complete( | ||
callable $onFulfilled = null, | ||
callable $onRejected = null | ||
):void { | ||
if(!is_null($this->result)) { | ||
$this->result->complete($onFulfilled, $onRejected); | ||
return; | ||
} | ||
|
||
array_push( | ||
$this->handlers, | ||
function(PromiseInterface $promise) use ($onFulfilled, $onRejected) { | ||
$promise->complete($onFulfilled, $onRejected); | ||
} | ||
); | ||
} | ||
|
||
private function resolver( | ||
callable $onFulfilled = null, | ||
callable $onRejected = null | ||
):callable { | ||
return function(callable $resolve, callable $reject) | ||
use ($onFulfilled, $onRejected):void { | ||
array_push( | ||
$this->handlers, | ||
function(PromiseInterface $promise) use ($onFulfilled, $onRejected, $resolve, $reject):void { | ||
$promise->then($onFulfilled, $onRejected) | ||
->complete($resolve, $reject); | ||
} | ||
); | ||
}; | ||
} | ||
|
||
private function reject(Throwable $reason):void { | ||
if(!is_null($this->result)) { | ||
return; | ||
} | ||
|
||
$this->settle(new RejectedPromise($reason)); | ||
} | ||
|
||
private function settle(PromiseInterface $result):void { | ||
$result = $this->unwrap($result); | ||
|
||
if($result === $this) { | ||
$result = new RejectedPromise( | ||
new PromiseException("A Promise must be settled with a concrete value, not another Promise.") | ||
); | ||
} | ||
|
||
$handlers = $this->handlers; | ||
|
||
$this->handlers = []; | ||
$this->result = $result; | ||
|
||
foreach($handlers as $handler) { | ||
call_user_func($handler, $result); | ||
} | ||
} | ||
|
||
/** | ||
* Obtains a reference to the ultimate promise in the chain. | ||
*/ | ||
private function unwrap(PromiseInterface $promise):PromiseInterface { | ||
while($promise instanceof self | ||
&& !is_null($promise->result)) { | ||
$promise = $promise->result; | ||
} | ||
|
||
return $promise; | ||
} | ||
|
||
private function call(callable $callback):void { | ||
try { | ||
call_user_func( | ||
$callback, | ||
/** @param mixed $value */ | ||
function($value = null) { | ||
$this->settle($this->resolve($value)); | ||
}, | ||
function(Throwable $reason) { | ||
$this->reject($reason); | ||
} | ||
); | ||
} | ||
catch(Throwable $reason) { | ||
$this->reject($reason); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
<?php | ||
namespace Gt\Promise; | ||
|
||
use RuntimeException; | ||
|
||
class PromiseException extends RuntimeException {} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,75 @@ | ||
<?php | ||
namespace Gt\Promise; | ||
|
||
use Throwable; | ||
|
||
class RejectedPromise implements PromiseInterface { | ||
use Resolvable; | ||
|
||
private Throwable $reason; | ||
|
||
public function __construct(Throwable $reason) { | ||
$this->reason = $reason; | ||
} | ||
|
||
public function then( | ||
callable $onFulfilled = null, | ||
callable $onRejected = null | ||
):PromiseInterface { | ||
if(is_null($onRejected)) { | ||
return $this; | ||
} | ||
|
||
return new Promise(function(callable $resolve, callable $reject) | ||
use ($onRejected):void { | ||
try { | ||
$resolve($onRejected($this->reason)); | ||
} | ||
catch(Throwable $reason) { | ||
$reject($reason); | ||
} | ||
}); | ||
} | ||
|
||
public function complete( | ||
callable $onFulfilled = null, | ||
callable $onRejected = null | ||
):void { | ||
if(null === $onRejected) { | ||
throw $this->reason; | ||
} | ||
|
||
$result = $onRejected($this->reason); | ||
|
||
if($result instanceof self) { | ||
throw $result->reason; | ||
} | ||
|
||
if($result instanceof PromiseInterface) { | ||
$result->complete(); | ||
} | ||
} | ||
|
||
public function catch( | ||
callable $onRejected | ||
):PromiseInterface { | ||
return $this->then(null, $onRejected); | ||
} | ||
|
||
public function finally( | ||
callable $onFulfilledOrRejected | ||
):PromiseInterface { | ||
return $this->then( | ||
null, | ||
function(Throwable $reason) | ||
use ($onFulfilledOrRejected):PromiseInterface { | ||
return $this->resolve($onFulfilledOrRejected()) | ||
->then(function() use ($reason):PromiseInterface { | ||
return new RejectedPromise( | ||
$reason | ||
); | ||
}); | ||
} | ||
); | ||
} | ||
} |
Oops, something went wrong.