├── LICENSE ├── README.md ├── compatibility.php ├── composer.json ├── example ├── ext-swoole │ ├── promise.php │ ├── promise_async.php │ ├── promise_async_all.php │ ├── promise_async_cycle.php │ ├── subpromise.php │ └── subpromise_async.php ├── promise.php └── subpromise.php └── lib ├── AbstractPromise.php ├── Exception ├── ExceptionInterface.php └── RuntimeException.php ├── ExtSwoolePromise.php ├── Factory.php ├── Promise.php ├── PromiseInterface.php └── WaitInterface.php /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2019-2020, StreamCommon 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | * Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | * Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | * Neither the name of the copyright holder nor the names of its 17 | contributors may be used to endorse or promote products derived from 18 | this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # PHP Promises/A+ implementation 2 | [![PHP >= 7.2 ][PHP image]](http://php.net) 3 | [![Swoole >= 4.2][Swoole image]](https://github.com/swoole/swoole-src) 4 | [![Latest Stable Version](https://poser.pugx.org/streamcommon/promise/v/stable)](https://packagist.org/packages/streamcommon/promise) 5 | [![Total Downloads](https://poser.pugx.org/streamcommon/promise/downloads)](https://packagist.org/packages/streamcommon/promise) 6 | [![License](https://poser.pugx.org/streamcommon/promise/license)](./LICENSE) 7 | 8 | This package provides [Promise/A+](https://github.com/promises-aplus/promises-spec) PHP implementation. 9 | 10 | # Branches 11 | [![Master][Master branch image]][Master branch] [![Build Status][Master image]][Master] [![Coverage Status][Master coverage image]][Master coverage] 12 | 13 | [![Develop][Develop branch image]][Develop branch] [![Build Status][Develop image]][Develop] [![Coverage Status][Develop coverage image]][Develop coverage] 14 | 15 | ## Installation 16 | Console run: 17 | ```console 18 | composer require streamcommon/promise 19 | ``` 20 | Or add into your `composer.json`: 21 | ```json 22 | "require": { 23 | "streamcommon/promise": "*" 24 | } 25 | ``` 26 | > If you want see TRUE promise then install [Swoole](http://php.net/manual/en/swoole.installation.php) extension. 27 | > For more info visit the [Swoole repo](https://github.com/swoole/swoole-src) 28 | >> NOTE: TRUE promise work only in CLI mode 29 | 30 | ## Promise 31 | Promise is a library which provides [Promise/A+](https://github.com/promises-aplus/promises-spec) PHP implementation. 32 | 33 | All Promise it a special PHP classes that contains its state: 34 | - `pending` - PromiseInterface::STATE_PENDING 35 | - `fulfilled` - PromiseInterface::STATE_FULFILLED 36 | - `rejected` - PromiseInterface::STATE_REJECTED 37 | 38 | To initiate a new promise, you can use static method `PromiseInterface::create` or create with new. 39 | All resulting `Promise` has `PromiseInterface::STATE_PENDING` state. 40 | ```php 41 | $promise = new Promise(function(callable $resolve, callable $reject)); 42 | // OR 43 | $promise = Promise::create(function(callable $resolve, callable $reject)) 44 | ``` 45 | 46 | When `function($resolve, $reject)` executor finishes the job, it should call one of the functions: 47 | - `$resolve` to indicate that the job finished successfully and set `Promise` state to `PromiseInterface::STATE_FULFILLED` 48 | ```php 49 | $resolve = function ($value) { 50 | $this->setState(PromiseInterface::STATE_FULFILLED); 51 | $this->setResult($value); 52 | }; 53 | ``` 54 | - `$reject` to indicate that an error occurred and set `Promise` state to `PromiseInterface::STATE_REJECTED` 55 | ```php 56 | $reject = function ($value) { 57 | $this->setState(PromiseInterface::STATE_REJECTED); 58 | $this->setResult($value); 59 | }; 60 | ``` 61 | 62 | Method `PromiseInterface::then()` it be called after promise change stage. In terms of our analogy: this is the “subscription". 63 | ```php 64 | public function then(?callable $onFulfilled = null, ?callable $onRejected = null): PromiseInterface; 65 | ``` 66 | - `$onFulfilled` run when the `Promise` is resolved and it has `PromiseInterface::STATE_FULFILLED` state. 67 | - `$onFulfilled` run when the `Promise` is rejected and it has `PromiseInterface::STATE_REJECTED` state. 68 | > NOTE: If `$onFulfilled` or `$onFulfilled` is not a callable function it was ignore 69 | 70 | Calling `PromiseInterface::resolve()` creates a successfully executed promise with the result value. 71 | ```php 72 | public static function resolve($value): PromiseInterface; 73 | ``` 74 | It is similar to: 75 | ```php 76 | $promise = new Promise(function(callable $resolve) { 77 | $resolve($value) 78 | }); 79 | ``` 80 | Similarly `PromiseInterface::reject()` creates an already executed promise with an error value. 81 | ```php 82 | public static function reject($value): PromiseInterface; 83 | ``` 84 | It is similar to: 85 | ```php 86 | $promise = new Promise(function(callable $resolve, callable $reject) { 87 | $reject($value) 88 | }); 89 | ``` 90 | ## Sub promise 91 | When `function($resolve, $reject)` executor finishes the job, it can return `PromiseInterface`. 92 | ```php 93 | $promise = Promise::create(function (callable $resolve) { 94 | $resolve(Promise::create(function (callable $subResolve) { 95 | $subResolve(42); 96 | })); 97 | }); 98 | ``` 99 | In this case, it will wait for the execution of sub promise. 100 | 101 | Method `PromiseInterface::then()` can return `PromiseInterface` to. 102 | ```php 103 | $promise->then(function ($value) { 104 | return Promise::create(function (callable $resolve) use ($value) { 105 | $resolve($value + 1); 106 | }); 107 | }); 108 | ``` 109 | For more info check [example](/example) scripts. 110 | 111 | ## Example 112 | 113 | > Standard Promise 114 | ```php 115 | use Streamcommon\Promise\Promise; 116 | 117 | $promise = Promise::create(function (callable $resolve) { 118 | $resolve(41); 119 | }); 120 | $newPromise = $promise->then(function ($value) { 121 | return $value + 1; 122 | }); 123 | $promise->then(function ($value) { 124 | echo $value . ' === 41' . PHP_EOL; 125 | }); 126 | $newPromise->then(function ($value) { 127 | echo $value . ' === 42' . PHP_EOL; 128 | }); 129 | $promise->wait(); // promise execution 130 | ``` 131 | 132 | > If you want see TRUE promise then install [Swoole](http://php.net/manual/en/swoole.installation.php) extension. 133 | > For more info visit the [Swoole repo](https://github.com/swoole/swoole-src) 134 | >> NOTE: TRUE promise work only in CLI mode 135 | 136 | ```php 137 | use Streamcommon\Promise\ExtSwoolePromise; 138 | 139 | // be careful with this 140 | \Swoole\Runtime::enableCoroutine(); // IF YOU WANT REALY ASYNC 141 | 142 | $promise = ExtSwoolePromise::create(function (callable $resolve) { 143 | // the function is executed automatically when the promise is constructed 144 | $resolve(41); 145 | }); 146 | $promise->then(function ($value) { 147 | // the function is executed automatically after __constructor job 148 | return $value + 1; 149 | })->then(function ($value) { 150 | // the function is executed automatically after ::then() 151 | echo $value . PHP_EOL; 152 | }); 153 | ``` 154 | > Sub promise 155 | ```php 156 | use Streamcommon\Promise\Promise; 157 | 158 | $promise = Promise::create(function (callable $resolve) { 159 | $resolve(Promise::create(function (callable $resolve) { 160 | $resolve(42); 161 | })); 162 | }); 163 | $newPromise = $promise->then(function ($value) { 164 | return $value + 1; 165 | }); 166 | $superNewPromise = $promise->then(function ($value) { 167 | return Promise::create(function (callable $resolve) use ($value) { 168 | $resolve($value + 2); 169 | }); 170 | }); 171 | $promise->then(function ($value) { 172 | echo $value . ' === 42' . PHP_EOL; 173 | }); 174 | $newPromise->then(function ($value) { 175 | echo $value . ' === 43' . PHP_EOL; 176 | }); 177 | $superNewPromise->then(function ($value) { 178 | echo $value . ' === 44' . PHP_EOL; 179 | }); 180 | $promise->wait(); 181 | ``` 182 | > Sub async promise 183 | ```php 184 | use Streamcommon\Promise\ExtSwoolePromise; 185 | 186 | // be careful with this 187 | \Swoole\Runtime::enableCoroutine(); // IF YOU WANT REALY ASYNC 188 | 189 | $promise = ExtSwoolePromise::create(function (callable $resolve) { 190 | $promise = ExtSwoolePromise::create(function (callable $resolve) { 191 | $resolve(41); 192 | }); 193 | $promise->then(function ($value) use ($resolve) { 194 | $resolve($value); 195 | }); 196 | }); 197 | $promise->then(function ($value) { 198 | return $value + 1; 199 | })->then(function ($value) { 200 | echo $value . PHP_EOL; 201 | }); 202 | ``` 203 | > If use `ExtSwoolePromise` with `daemon|cycle|loop` you must use `Swoole\Runtime::wait()` 204 | ```php 205 | \Swoole\Runtime::enableCoroutine(); 206 | while (true) { 207 | /// 208 | Some code with ExtSwoolePromise 209 | /// 210 | \Swoole\Runtime::wait(); 211 | } 212 | ``` 213 | [PHP image]: https://img.shields.io/badge/php-%3E%3D%207.2-blue.svg 214 | [Swoole image]: https://img.shields.io/badge/swoole-%3E%3D%204.2-blue.svg 215 | [Master branch]: https://github.com/streamcommon/promise/tree/master 216 | [Master branch image]: https://img.shields.io/badge/branch-master-blue.svg 217 | [Develop branch]: https://github.com/streamcommon/promise/tree/develop 218 | [Develop branch image]: https://img.shields.io/badge/branch-develop-blue.svg 219 | [Master image]: https://travis-ci.org/streamcommon/promise.svg?branch=master 220 | [Master]: https://travis-ci.org/streamcommon/promise 221 | [Master coverage image]: https://coveralls.io/repos/github/streamcommon/promise/badge.svg?branch=master 222 | [Master coverage]: https://coveralls.io/github/streamcommon/promise?branch=master 223 | [Develop image]: https://travis-ci.org/streamcommon/promise.svg?branch=develop 224 | [Develop]: https://travis-ci.org/streamcommon/promise 225 | [Develop coverage image]: https://coveralls.io/repos/github/streamcommon/promise/badge.svg?branch=develop 226 | [Develop coverage]: https://coveralls.io/github/streamcommon/promise?branch=develop -------------------------------------------------------------------------------- /compatibility.php: -------------------------------------------------------------------------------- 1 | then(function ($value) { 32 | sleep(2); 33 | return $value + 1; 34 | }); 35 | $promise3 = $promise1->then(function ($value) { 36 | sleep(1); 37 | throw new \Exception('error'); 38 | }); 39 | $promise4 = $promise1->then(function ($value) { 40 | return ExtSwoolePromise::create(function (callable $resolver) use ($value) { 41 | sleep(3); 42 | $resolver($value + 5); 43 | }); 44 | }); 45 | ########### INIT ############## 46 | 47 | ########### RESULT ############## 48 | $promise1->then(function ($value) { 49 | echo $value . ' === 41' . PHP_EOL; 50 | }); 51 | $promise2->then(function ($value) { 52 | echo $value . ' === 42' . PHP_EOL; 53 | }); 54 | $promise3->then(null, function ($error) { 55 | echo 'instanceof Throwable === ' . ($error instanceof Throwable) . PHP_EOL; 56 | }); 57 | $promise4->then(function ($value) { 58 | echo $value . ' === 46' . PHP_EOL; 59 | }); 60 | ########### RESULT ############## 61 | -------------------------------------------------------------------------------- /example/ext-swoole/promise_async.php: -------------------------------------------------------------------------------- 1 | then(function ($value) { 34 | sleep(10); 35 | return $value + 1; 36 | }); 37 | $promise3 = $promise1->then(function ($value) { 38 | sleep(1); 39 | throw new \Exception('error'); 40 | }); 41 | $promise4 = $promise1->then(function ($value) { 42 | return ExtSwoolePromise::create(function (callable $resolver) use ($value) { 43 | sleep(3); 44 | $resolver($value + 5); 45 | }); 46 | }); 47 | ########### INIT ############## 48 | 49 | ########### RESULT ############## 50 | $promise1->then(function ($value) { 51 | echo $value . ' === 41' . PHP_EOL; 52 | }); 53 | $promise2->then(function ($value) { 54 | echo $value . ' === 42' . PHP_EOL; 55 | }); 56 | $promise3->then(null, function ($error) { 57 | echo 'instanceof Throwable === ' . ($error instanceof Throwable) . PHP_EOL; 58 | }); 59 | $promise4->then(function ($value) { 60 | echo $value . ' === 46' . PHP_EOL; 61 | }); 62 | ########### RESULT ############## 63 | -------------------------------------------------------------------------------- /example/ext-swoole/promise_async_all.php: -------------------------------------------------------------------------------- 1 | then(function ($value) { 40 | echo $value[0] . ' === 41' . PHP_EOL; 41 | echo $value[1] . ' === 42' . PHP_EOL; 42 | }); 43 | -------------------------------------------------------------------------------- /example/ext-swoole/promise_async_cycle.php: -------------------------------------------------------------------------------- 1 | then(function ($value) { 33 | sleep(2); 34 | return $value + 1; 35 | }); 36 | $promise3 = $promise1->then(function ($value) { 37 | sleep(1); 38 | throw new \Exception('error'); 39 | }); 40 | $promise4 = $promise1->then(function ($value) { 41 | return ExtSwoolePromise::create(function (callable $resolver) use ($value) { 42 | sleep(3); 43 | $resolver($value + 5); 44 | }); 45 | }); 46 | ########### INIT ############## 47 | $c = 0; 48 | while (true) { 49 | if ($c > 0) { 50 | break; 51 | } 52 | ########### RESULT ############## 53 | $promise1->then(function ($value) { 54 | echo $value . ' === 41' . PHP_EOL; 55 | }); 56 | $promise2->then(function ($value) { 57 | echo $value . ' === 42' . PHP_EOL; 58 | }); 59 | $promise3->then(null, function ($error) { 60 | echo 'instanceof Throwable === ' . ($error instanceof Throwable) . PHP_EOL; 61 | }); 62 | $promise4->then(function ($value) { 63 | echo $value . ' === 46' . PHP_EOL; 64 | }); 65 | ########### RESULT ############## 66 | $c++; 67 | Event::wait(); // IF USE DAEMON MODE 68 | } 69 | -------------------------------------------------------------------------------- /example/ext-swoole/subpromise.php: -------------------------------------------------------------------------------- 1 | then(function ($value) use ($resolve) { 34 | $resolve($value); 35 | }); 36 | }); 37 | $promise2 = $promise->then(function ($value) { 38 | sleep(3); 39 | return $value + 1; 40 | }); 41 | $promise->then(function ($value) { 42 | echo $value . ' === 41' . PHP_EOL; 43 | }); 44 | $promise2->then(function ($value) { 45 | echo $value . ' === 42' . PHP_EOL; 46 | }); 47 | ############################################################# 48 | 49 | ############################################################# 50 | $promise = ExtSwoolePromise::create(function (callable $resolve) { 51 | $resolve(ExtSwoolePromise::create(function (callable $resolve) { 52 | sleep(2); 53 | $resolve(42); 54 | })); 55 | }); 56 | $promise2 = $promise->then(function ($value) { 57 | return $value + 1; 58 | }); 59 | $promise->then(function ($value) { 60 | sleep(1); 61 | echo $value . ' === 42' . PHP_EOL; 62 | }); 63 | $promise2->then(function ($value) { 64 | echo $value . ' === 43' . PHP_EOL; 65 | }); 66 | ############################################################# 67 | 68 | ############################################################# 69 | $promise = ExtSwoolePromise::create(function (callable $resolve) { 70 | $resolve(43); 71 | }); 72 | $promise2 = $promise->then(function ($value) { 73 | sleep(1); 74 | return ExtSwoolePromise::create(function (callable $resolve) use ($value) { 75 | $resolve($value + 1); 76 | })->then(function ($value) { 77 | return $value + 1; 78 | }); 79 | }); 80 | $promise->then(function ($value) { 81 | echo $value . ' === 43' . PHP_EOL; 82 | }); 83 | $promise2->then(function ($value) { 84 | echo $value . ' === 45' . PHP_EOL; 85 | }); 86 | ############################################################# 87 | -------------------------------------------------------------------------------- /example/ext-swoole/subpromise_async.php: -------------------------------------------------------------------------------- 1 | then(function ($value) use ($resolve) { 37 | sleep(1); 38 | $resolve($value); 39 | }); 40 | }); 41 | $promise2 = $promise->then(function ($value) { 42 | sleep(3); 43 | return $value + 1; 44 | }); 45 | $promise->then(function ($value) { 46 | echo $value . ' === 41' . PHP_EOL; 47 | }); 48 | $promise2->then(function ($value) { 49 | echo $value . ' === 42' . PHP_EOL; 50 | }); 51 | ############################################################# 52 | 53 | ############################################################# 54 | $promise = ExtSwoolePromise::create(function (callable $resolve) { 55 | $resolve(ExtSwoolePromise::create(function (callable $resolve) { 56 | sleep(3); 57 | $resolve(42); 58 | })); 59 | }); 60 | $promise2 = $promise->then(function ($value) { 61 | return $value + 1; 62 | }); 63 | $promise->then(function ($value) { 64 | echo $value . ' === 42' . PHP_EOL; 65 | }); 66 | $promise2->then(function ($value) { 67 | echo $value . ' === 43' . PHP_EOL; 68 | }); 69 | ############################################################# 70 | 71 | ############################################################# 72 | $promise = ExtSwoolePromise::create(function (callable $resolve) { 73 | $resolve(43); 74 | }); 75 | $promise2 = $promise->then(function ($value) { 76 | sleep(1); 77 | return ExtSwoolePromise::create(function (callable $resolve) use ($value) { 78 | $resolve($value + 1); 79 | })->then(function ($value) { 80 | return $value + 1; 81 | }); 82 | }); 83 | $promise->then(function ($value) { 84 | echo $value . ' === 43' . PHP_EOL; 85 | }); 86 | $promise2->then(function ($value) { 87 | echo $value . ' === 45' . PHP_EOL; 88 | }); 89 | ############################################################# 90 | -------------------------------------------------------------------------------- /example/promise.php: -------------------------------------------------------------------------------- 1 | then(function ($value) { 28 | return $value + 1; 29 | }); 30 | $promise3 = $promise1->then(function ($value) { 31 | throw new \Exception('error'); 32 | }); 33 | $promise4 = $promise1->then(function ($value) { 34 | return Promise::create(function (callable $resolver) use ($value) { 35 | $resolver($value + 5); 36 | }); 37 | }); 38 | ########### INIT ############## 39 | 40 | ########### RESULT ############## 41 | $promise1->then(function ($value) { 42 | echo $value . ' === 41' . PHP_EOL; 43 | }); 44 | $promise2->then(function ($value) { 45 | echo $value . ' === 42' . PHP_EOL; 46 | }); 47 | $promise3->then(null, function ($error) { 48 | echo 'instanceof Throwable === ' . ($error instanceof Throwable) . PHP_EOL; 49 | }); 50 | $promise4->then(function ($value) { 51 | echo $value . ' === 46' . PHP_EOL; 52 | }); 53 | ########### RESULT ############## 54 | if ($promise1 instanceof WaitInterface) { 55 | $promise1->wait(); 56 | } 57 | -------------------------------------------------------------------------------- /example/subpromise.php: -------------------------------------------------------------------------------- 1 | then(function ($value) use ($resolve) { 31 | $resolve($value); 32 | }); 33 | if ($promise instanceof WaitInterface) { 34 | $promise->wait(); 35 | } 36 | }); 37 | $promise2 = $promise->then(function ($value) { 38 | return $value + 1; 39 | }); 40 | $promise->then(function ($value) { 41 | echo $value . ' === 41' . PHP_EOL; 42 | }); 43 | $promise2->then(function ($value) { 44 | echo $value . ' === 42' . PHP_EOL; 45 | }); 46 | if ($promise instanceof WaitInterface) { 47 | $promise->wait(); 48 | } 49 | ############################################################# 50 | 51 | ############################################################# 52 | $promise = Promise::create(function (callable $resolve) { 53 | $resolve(Promise::create(function (callable $resolve) { 54 | $resolve(42); 55 | })); 56 | }); 57 | $promise2 = $promise->then(function ($value) { 58 | return $value + 1; 59 | }); 60 | $promise->then(function ($value) { 61 | echo $value . ' === 42' . PHP_EOL; 62 | }); 63 | $promise2->then(function ($value) { 64 | echo $value . ' === 43' . PHP_EOL; 65 | }); 66 | if ($promise instanceof WaitInterface) { 67 | $promise->wait(); 68 | } 69 | ############################################################# 70 | 71 | ############################################################# 72 | $promise = Promise::create(function (callable $resolve) { 73 | $resolve(43); 74 | }); 75 | $promise2 = $promise->then(function ($value) { 76 | // HAVE BIG PROBLEM 77 | return Promise::create(function (callable $resolve) use ($value) { 78 | $resolve($value + 1); 79 | })->then(function ($value) { 80 | return $value + 1; 81 | }); 82 | }); 83 | $promise->then(function ($value) { 84 | echo $value . ' === 43' . PHP_EOL; 85 | }); 86 | $promise2->then(function ($value) { 87 | echo $value . ' === 45' . PHP_EOL; 88 | }); 89 | if ($promise instanceof WaitInterface) { 90 | $promise->wait(); 91 | } 92 | ############################################################# 93 | -------------------------------------------------------------------------------- /lib/AbstractPromise.php: -------------------------------------------------------------------------------- 1 | then(null, $onRejected); 82 | } 83 | 84 | /** 85 | * Change promise state 86 | * 87 | * @param integer $state 88 | * @return void 89 | */ 90 | final protected function setState(int $state): void 91 | { 92 | $this->state = $state; 93 | } 94 | 95 | /** 96 | * Promise is pending 97 | * 98 | * @return boolean 99 | */ 100 | final protected function isPending(): bool 101 | { 102 | return $this->state == self::STATE_PENDING; 103 | } 104 | 105 | /** 106 | * Promise is fulfilled 107 | * 108 | * @return boolean 109 | */ 110 | final protected function isFulfilled(): bool 111 | { 112 | return $this->state == self::STATE_FULFILLED; 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /lib/Exception/ExceptionInterface.php: -------------------------------------------------------------------------------- 1 | setResult($value); 51 | $this->setState(self::STATE_FULFILLED); 52 | }; 53 | $reject = function ($value) { 54 | if ($this->isPending()) { 55 | $this->setResult($value); 56 | $this->setState(self::STATE_REJECTED); 57 | } 58 | }; 59 | Coroutine::create(function (callable $executor, callable $resolve, callable $reject) { 60 | try { 61 | $executor($resolve, $reject); 62 | } catch (\Throwable $exception) { 63 | $reject($exception); 64 | } 65 | }, $executor, $resolve, $reject); 66 | } 67 | 68 | /** 69 | * {@inheritDoc} 70 | * 71 | * @param callable|null $onFulfilled 72 | * @param callable|null $onRejected 73 | * @return ExtSwoolePromise 74 | */ 75 | public function then(?callable $onFulfilled = null, ?callable $onRejected = null): PromiseInterface 76 | { 77 | return self::create(function (callable $resolve, callable $reject) use ($onFulfilled, $onRejected) { 78 | while ($this->isPending()) { 79 | // @codeCoverageIgnoreStart 80 | usleep(PROMISE_WAIT); 81 | // @codeCoverageIgnoreEnd 82 | } 83 | $callable = $this->isFulfilled() ? $onFulfilled : $onRejected; 84 | if (!is_callable($callable)) { 85 | $resolve($this->result); 86 | return; 87 | } 88 | try { 89 | $resolve($callable($this->result)); 90 | } catch (\Throwable $error) { 91 | $reject($error); 92 | } 93 | }); 94 | } 95 | 96 | /** 97 | * {@inheritDoc} 98 | * 99 | * @param iterable|ExtSwoolePromise[] $promises 100 | * @return ExtSwoolePromise 101 | */ 102 | public static function all(iterable $promises): PromiseInterface 103 | { 104 | return self::create(function (callable $resolve, callable $reject) use ($promises) { 105 | $ticks = count($promises); 106 | 107 | $firstError = null; 108 | $channel = new Channel($ticks); 109 | $result = new ArrayCollection(); 110 | $key = 0; 111 | foreach ($promises as $promise) { 112 | if (!$promise instanceof ExtSwoolePromise) { 113 | $channel->close(); 114 | throw new Exception\RuntimeException( 115 | 'Supported only Streamcommon\Promise\ExtSwoolePromise instance' 116 | ); 117 | } 118 | $promise->then(function ($value) use ($key, $result, $channel) { 119 | $result->set($key, $value); 120 | $channel->push(true); 121 | return $value; 122 | }, function ($error) use ($channel, &$firstError) { 123 | $channel->push(true); 124 | if ($firstError === null) { 125 | $firstError = $error; 126 | } 127 | }); 128 | $key++; 129 | } 130 | while ($ticks--) { 131 | $channel->pop(); 132 | } 133 | $channel->close(); 134 | 135 | if ($firstError !== null) { 136 | $reject($firstError); 137 | return; 138 | } 139 | 140 | $resolve($result); 141 | }); 142 | } 143 | 144 | /** 145 | * Set resolved result 146 | * 147 | * @param mixed $value 148 | * @return void 149 | */ 150 | private function setResult($value): void 151 | { 152 | if ($value instanceof PromiseInterface) { 153 | if (!$value instanceof ExtSwoolePromise) { 154 | throw new Exception\RuntimeException('Supported only Streamcommon\Promise\ExtSwoolePromise instance'); 155 | } 156 | $resolved = false; 157 | $callable = function ($value) use (&$resolved) { 158 | $this->setResult($value); 159 | $resolved = true; 160 | }; 161 | $value->then($callable, $callable); 162 | // resolve async locking error 163 | while (!$resolved) { 164 | // @codeCoverageIgnoreStart 165 | usleep(PROMISE_WAIT); 166 | // @codeCoverageIgnoreEnd 167 | } 168 | } else { 169 | $this->result = $value; 170 | } 171 | } 172 | } 173 | -------------------------------------------------------------------------------- /lib/Factory.php: -------------------------------------------------------------------------------- 1 | */ 25 | private $collection; 26 | /** @var callable */ 27 | private $executor; 28 | /** @var mixed */ 29 | private $result; 30 | 31 | /** 32 | * Promise constructor. 33 | * 34 | * @param callable $executor 35 | */ 36 | public function __construct(callable $executor) 37 | { 38 | $this->executor = $executor; 39 | $this->collection = new ArrayCollection(); 40 | } 41 | 42 | /** 43 | * It be called after promise change stage 44 | * 45 | * @param callable|null $onFulfilled 46 | * @param callable|null $onRejected 47 | * @return Promise 48 | */ 49 | public function then(?callable $onFulfilled = null, ?callable $onRejected = null): PromiseInterface 50 | { 51 | /** @var Promise $promise */ 52 | $promise = self::create(function (callable $resolve, callable $reject) use ($onFulfilled, $onRejected) { 53 | if ($this->isPending()) { 54 | $this->wait(); 55 | } 56 | $callable = $this->isFulfilled() ? $onFulfilled : $onRejected; 57 | if (!is_callable($callable)) { 58 | $resolve($this->result); 59 | return; 60 | } 61 | try { 62 | $resolve($callable($this->result)); 63 | } catch (\Throwable $error) { 64 | $reject($error); 65 | } 66 | }); 67 | $this->collection->add($promise); 68 | return $promise; 69 | } 70 | 71 | /** 72 | * {@inheritDoc} 73 | * 74 | * @param iterable|Promise[] $promises 75 | * @return Promise 76 | */ 77 | public static function all(iterable $promises): PromiseInterface 78 | { 79 | return self::create(function (callable $resolve, callable $reject) use ($promises) { 80 | $result = new ArrayCollection(); 81 | $key = 0; 82 | $firstError = null; 83 | 84 | foreach ($promises as $promise) { 85 | if (!$promise instanceof Promise) { 86 | throw new Exception\RuntimeException('Supported only Streamcommon\Promise\Promise instance'); 87 | } 88 | $promise->then(function ($value) use ($key, $result) { 89 | $result->set($key, $value); 90 | return $value; 91 | }, function ($error) use (&$firstError) { 92 | if ($firstError !== null) { 93 | return; 94 | } 95 | 96 | $firstError = $error; 97 | }); 98 | $promise->wait(); 99 | $key++; 100 | } 101 | 102 | if ($firstError !== null) { 103 | $reject($firstError); 104 | return; 105 | } 106 | 107 | $resolve($result); 108 | }); 109 | } 110 | 111 | /** 112 | * {@inheritDoc} 113 | * 114 | * @return void 115 | */ 116 | public function wait(): void 117 | { 118 | $resolve = function ($value) { 119 | $this->setResult($value); 120 | $this->setState(self::STATE_FULFILLED); 121 | }; 122 | $reject = function ($value) { 123 | if ($this->isPending()) { 124 | $this->setResult($value); 125 | $this->setState(self::STATE_REJECTED); 126 | } 127 | }; 128 | try { 129 | ($this->executor)($resolve, $reject); 130 | } catch (\Throwable $exception) { 131 | $reject($exception); 132 | } 133 | foreach ($this->collection as $promise) { 134 | if ($promise instanceof WaitInterface) { 135 | $promise->wait(); 136 | } 137 | } 138 | $this->collection->clear(); 139 | } 140 | 141 | /** 142 | * Set resolved result 143 | * 144 | * @param mixed $value 145 | * @return void 146 | */ 147 | private function setResult($value): void 148 | { 149 | if ($value instanceof PromiseInterface) { 150 | if (!$value instanceof Promise) { 151 | throw new Exception\RuntimeException('Supported only Streamcommon\Promise\Promise instance'); 152 | } 153 | $callable = function ($value) { 154 | $this->setResult($value); 155 | }; 156 | $value->then($callable, $callable); 157 | $value->wait(); 158 | } else { 159 | $this->result = $value; 160 | } 161 | } 162 | 163 | /** 164 | * Destructor 165 | */ 166 | public function __destruct() 167 | { 168 | $this->collection->clear(); 169 | unset($this->collection); 170 | } 171 | } 172 | -------------------------------------------------------------------------------- /lib/PromiseInterface.php: -------------------------------------------------------------------------------- 1 |