Codebase list php-react-promise / 8d3bdac
New upstream version 2.7.0 Dominik George 5 years ago
12 changed file(s) with 555 addition(s) and 89 deletion(s). Raw diff Collapse all Expand all
44 - 5.5
55 - 5.6
66 - 7.0
7 - nightly
8 - hhvm
7 - 7.1
8 - nightly # ignore errors, see below
9 - hhvm # ignore errors, see below
910
10 before_install:
11 - composer self-update
11 # lock distro so new future defaults will not break the build
12 dist: trusty
13
14 matrix:
15 allow_failures:
16 - php: hhvm
17 - php: nightly
1218
1319 install:
1420 - composer install
00 CHANGELOG for 2.x
11 =================
2
3 * 2.7.0 (2018-06-13)
4
5 * Feature: Improve memory consumption for pending promises by using static internal callbacks without binding to self.
6 (#124 by @clue)
7
8 * 2.6.0 (2018-06-11)
9
10 * Feature: Significantly improve memory consumption and performance by only passing resolver args
11 to resolver and canceller if callback requires them. Also use static callbacks without
12 binding to promise, clean up canceller function reference when they are no longer
13 needed and hide resolver and canceller references from call stack on PHP 7+.
14 (#113, #115, #116, #117, #118, #119 and #123 by @clue)
15
16 These changes combined mean that rejecting promises with an `Exception` should
17 no longer cause any internal circular references which could cause some unexpected
18 memory growth in previous versions. By explicitly avoiding and explicitly
19 cleaning up said references, we can avoid relying on PHP's circular garbage collector
20 to kick in which significantly improves performance when rejecting many promises.
21
22 * Mark legacy progress support / notification API as deprecated
23 (#112 by @clue)
24
25 * Recommend rejecting promises by throwing an exception
26 (#114 by @jsor)
27
28 * Improve documentation to properly instantiate LazyPromise
29 (#121 by @holtkamp)
30
31 * Follower cancellation propagation was originally planned for this release
32 but has been reverted for now and is planned for a future release.
33 (#99 by @jsor and #122 by @clue)
234
335 * 2.5.1 (2017-03-25)
436
0 React/Promise
1 =============
0 Promise
1 =======
22
33 A lightweight implementation of
44 [CommonJS Promises/A](http://wiki.commonjs.org/wiki/Promises/A) for PHP.
1212 1. [Introduction](#introduction)
1313 2. [Concepts](#concepts)
1414 * [Deferred](#deferred)
15 * [Promise](#promise)
15 * [Promise](#promise-1)
1616 3. [API](#api)
1717 * [Deferred](#deferred-1)
1818 * [Deferred::promise()](#deferredpromise)
2828 * [ExtendedPromiseInterface::progress()](#extendedpromiseinterfaceprogress)
2929 * [CancellablePromiseInterface](#cancellablepromiseinterface)
3030 * [CancellablePromiseInterface::cancel()](#cancellablepromiseinterfacecancel)
31 * [Promise](#promise-1)
31 * [Promise](#promise-2)
3232 * [FulfilledPromise](#fulfilledpromise)
3333 * [RejectedPromise](#rejectedpromise)
3434 * [LazyPromise](#lazypromise)
5050 * [Mixed resolution and rejection forwarding](#mixed-resolution-and-rejection-forwarding)
5151 * [Progress event forwarding](#progress-event-forwarding)
5252 * [done() vs. then()](#done-vs-then)
53 5. [Credits](#credits)
54 6. [License](#license)
53 5. [Install](#install)
54 6. [Credits](#credits)
55 7. [License](#license)
5556
5657 Introduction
5758 ------------
5859
59 React/Promise is a library implementing
60 Promise is a library implementing
6061 [CommonJS Promises/A](http://wiki.commonjs.org/wiki/Promises/A) for PHP.
6162
6263 It also provides several other useful promise-related concepts, such as joining
102103
103104 The `resolve` and `reject` methods control the state of the deferred.
104105
105 The `notify` method is for progress notification.
106 The deprecated `notify` method is for progress notification.
106107
107108 The constructor of the `Deferred` accepts an optional `$canceller` argument.
108 See [Promise](#promise-1) for more information.
109 See [Promise](#promise-2) for more information.
109110
110111 #### Deferred::promise()
111112
145146
146147 #### Deferred::notify()
147148
149 > Deprecated in v2.6.0: Progress support is deprecated and should not be used anymore.
150
148151 ```php
149152 $deferred->notify(mixed $update = null);
150153 ```
168171
169172 #### Implementations
170173
171 * [Promise](#promise-1)
174 * [Promise](#promise-2)
172175 * [FulfilledPromise](#fulfilledpromise)
173176 * [RejectedPromise](#rejectedpromise)
174177 * [LazyPromise](#lazypromise)
189192 the result as the first argument.
190193 * `$onRejected` will be invoked once the promise is rejected and passed the
191194 reason as the first argument.
192 * `$onProgress` will be invoked whenever the producer of the promise
195 * `$onProgress` (deprecated) will be invoked whenever the producer of the promise
193196 triggers progress notifications and passed a single argument (whatever it
194197 wants) to indicate progress.
195198
204207 never both.
205208 2. `$onFulfilled` and `$onRejected` will never be called more
206209 than once.
207 3. `$onProgress` may be called multiple times.
210 3. `$onProgress` (deprecated) may be called multiple times.
208211
209212 #### See also
210213
320323
321324 #### ExtendedPromiseInterface::progress()
322325
326 > Deprecated in v2.6.0: Progress support is deprecated and should not be used anymore.
327
323328 ```php
324329 $promise->progress(callable $onProgress);
325330 ```
363368 ```php
364369 $resolver = function (callable $resolve, callable $reject, callable $notify) {
365370 // Do some work, possibly asynchronously, and then
366 // resolve or reject. You can notify of progress events
371 // resolve or reject. You can notify of progress events (deprecated)
367372 // along the way if you want/need.
368373
369374 $resolve($awesomeResult);
375 // or throw new Exception('Promise rejected');
370376 // or $resolve($anotherPromise);
371377 // or $reject($nastyError);
372378 // or $notify($progressNotification);
373379 };
374380
375 $canceller = function (callable $resolve, callable $reject, callable $progress) {
381 $canceller = function () {
376382 // Cancel/abort any running operations like network connections, streams etc.
377383
378 $reject(new \Exception('Promise cancelled'));
384 // Reject promise by throwing an exception
385 throw new Exception('Promise cancelled');
379386 };
380387
381388 $promise = new React\Promise\Promise($resolver, $canceller);
389396 When called with a non-promise value, fulfills promise with that value.
390397 When called with another promise, e.g. `$resolve($otherPromise)`, promise's
391398 fate will be equivalent to that of `$otherPromise`.
392 * `$reject($reason)` - Function that rejects the promise.
393 * `$notify($update)` - Function that issues progress events for the promise.
399 * `$reject($reason)` - Function that rejects the promise. It is recommended to
400 just throw an exception instead of using `$reject()`.
401 * `$notify($update)` - Deprecated function that issues progress events for the promise.
394402
395403 If the resolver or canceller throw an exception, the promise will be rejected
396404 with that thrown exception as the rejection reason.
434442 return $deferred->promise();
435443 };
436444
437 $promise = React\Promise\LazyPromise($factory);
445 $promise = new React\Promise\LazyPromise($factory);
438446
439447 // $factory will only be executed once we call then()
440448 $promise->then(function ($value) {
719727 ```
720728
721729 #### Progress event forwarding
730
731 > Deprecated in v2.6.0: Progress support is deprecated and should not be used anymore.
722732
723733 In the same way as resolution and rejection handlers, your progress handler
724734 **MUST** return a progress event to be propagated to the next link in the chain.
823833
824834 You can get the original rejection reason by calling `$exception->getReason()`.
825835
836 Install
837 -------
838
839 The recommended way to install this library is [through Composer](https://getcomposer.org).
840 [New to Composer?](https://getcomposer.org/doc/00-intro.md)
841
842 This project follows [SemVer](https://semver.org/).
843 This will install the latest supported version:
844
845 ```bash
846 $ composer require react/promise:^2.7
847 ```
848
849 See also the [CHANGELOG](CHANGELOG.md) for details about version upgrades.
850
851 This project aims to run on any platform and thus does not require any PHP
852 extensions and supports running on legacy PHP 5.4 through current PHP 7+ and HHVM.
853 It's *highly recommended to use PHP 7+* for this project due to its vast
854 performance improvements.
855
826856 Credits
827857 -------
828858
829 React/Promise is a port of [when.js](https://github.com/cujojs/when)
859 Promise is a port of [when.js](https://github.com/cujojs/when)
830860 by [Brian Cavalier](https://github.com/briancavalier).
831861
832862 Also, large parts of the documentation have been ported from the when.js
836866 License
837867 -------
838868
839 React/Promise is released under the [MIT](https://github.com/reactphp/promise/blob/master/LICENSE) license.
869 Released under the [MIT](LICENSE) license.
2222 $this->rejectCallback = $reject;
2323 $this->notifyCallback = $notify;
2424 }, $this->canceller);
25 $this->canceller = null;
2526 }
2627
2728 return $this->promise;
4142 call_user_func($this->rejectCallback, $reason);
4243 }
4344
45 /**
46 * @deprecated 2.6.0 Progress support is deprecated and should not be used anymore.
47 * @param mixed $update
48 */
4449 public function notify($update = null)
4550 {
4651 $this->promise();
44 interface ExtendedPromiseInterface extends PromiseInterface
55 {
66 /**
7 *
8 * The `$onProgress` argument is deprecated and should not be used anymore.
9 *
710 * @return void
811 */
912 public function done(callable $onFulfilled = null, callable $onRejected = null, callable $onProgress = null);
2023
2124 /**
2225 * @return ExtendedPromiseInterface
26 * @deprecated 2.6.0 Progress support is deprecated and should not be used anymore.
2327 */
2428 public function progress(callable $onProgress);
2529 }
1515 public function __construct(callable $resolver, callable $canceller = null)
1616 {
1717 $this->canceller = $canceller;
18 $this->call($resolver);
18
19 // Explicitly overwrite arguments with null values before invoking
20 // resolver function. This ensure that these arguments do not show up
21 // in the stack trace in PHP 7+ only.
22 $cb = $resolver;
23 $resolver = $canceller = null;
24 $this->call($cb);
1925 }
2026
2127 public function then(callable $onFulfilled = null, callable $onRejected = null, callable $onProgress = null)
2834 return new static($this->resolver($onFulfilled, $onRejected, $onProgress));
2935 }
3036
31 $this->requiredCancelRequests++;
32
33 return new static($this->resolver($onFulfilled, $onRejected, $onProgress), function () {
34 if (++$this->cancelRequests < $this->requiredCancelRequests) {
35 return;
36 }
37
38 $this->cancel();
39 });
37 // This promise has a canceller, so we create a new child promise which
38 // has a canceller that invokes the parent canceller if all other
39 // followers are also cancelled. We keep a reference to this promise
40 // instance for the static canceller function and clear this to avoid
41 // keeping a cyclic reference between parent and follower.
42 $parent = $this;
43 ++$parent->requiredCancelRequests;
44
45 return new static(
46 $this->resolver($onFulfilled, $onRejected, $onProgress),
47 static function () use (&$parent) {
48 if (++$parent->cancelRequests >= $parent->requiredCancelRequests) {
49 $parent->cancel();
50 }
51
52 $parent = null;
53 }
54 );
4055 }
4156
4257 public function done(callable $onFulfilled = null, callable $onRejected = null, callable $onProgress = null)
4560 return $this->result->done($onFulfilled, $onRejected, $onProgress);
4661 }
4762
48 $this->handlers[] = function (ExtendedPromiseInterface $promise) use ($onFulfilled, $onRejected) {
63 $this->handlers[] = static function (ExtendedPromiseInterface $promise) use ($onFulfilled, $onRejected) {
4964 $promise
5065 ->done($onFulfilled, $onRejected);
5166 };
5772
5873 public function otherwise(callable $onRejected)
5974 {
60 return $this->then(null, function ($reason) use ($onRejected) {
75 return $this->then(null, static function ($reason) use ($onRejected) {
6176 if (!_checkTypehint($onRejected, $reason)) {
6277 return new RejectedPromise($reason);
6378 }
6883
6984 public function always(callable $onFulfilledOrRejected)
7085 {
71 return $this->then(function ($value) use ($onFulfilledOrRejected) {
86 return $this->then(static function ($value) use ($onFulfilledOrRejected) {
7287 return resolve($onFulfilledOrRejected())->then(function () use ($value) {
7388 return $value;
7489 });
75 }, function ($reason) use ($onFulfilledOrRejected) {
90 }, static function ($reason) use ($onFulfilledOrRejected) {
7691 return resolve($onFulfilledOrRejected())->then(function () use ($reason) {
7792 return new RejectedPromise($reason);
7893 });
100115 {
101116 return function ($resolve, $reject, $notify) use ($onFulfilled, $onRejected, $onProgress) {
102117 if ($onProgress) {
103 $progressHandler = function ($update) use ($notify, $onProgress) {
118 $progressHandler = static function ($update) use ($notify, $onProgress) {
104119 try {
105120 $notify($onProgress($update));
106121 } catch (\Throwable $e) {
113128 $progressHandler = $notify;
114129 }
115130
116 $this->handlers[] = function (ExtendedPromiseInterface $promise) use ($onFulfilled, $onRejected, $resolve, $reject, $progressHandler) {
131 $this->handlers[] = static function (ExtendedPromiseInterface $promise) use ($onFulfilled, $onRejected, $resolve, $reject, $progressHandler) {
117132 $promise
118133 ->then($onFulfilled, $onRejected)
119134 ->done($resolve, $reject, $progressHandler);
123138 };
124139 }
125140
126 private function resolve($value = null)
141 private function reject($reason = null)
127142 {
128143 if (null !== $this->result) {
129144 return;
130145 }
131146
132 $this->settle(resolve($value));
133 }
134
135 private function reject($reason = null)
136 {
137 if (null !== $this->result) {
138 return;
139 }
140
141147 $this->settle(reject($reason));
142148 }
143149
144 private function notify($update = null)
145 {
146 if (null !== $this->result) {
147 return;
148 }
149
150 foreach ($this->progressHandlers as $handler) {
151 $handler($update);
152 }
153 }
154
155150 private function settle(ExtendedPromiseInterface $promise)
156151 {
157152 $promise = $this->unwrap($promise);
153
154 if ($promise === $this) {
155 $promise = new RejectedPromise(
156 new \LogicException('Cannot resolve a promise with itself.')
157 );
158 }
158159
159160 $handlers = $this->handlers;
160161
161162 $this->progressHandlers = $this->handlers = [];
162163 $this->result = $promise;
164 $this->canceller = null;
163165
164166 foreach ($handlers as $handler) {
165167 $handler($promise);
183185 $promise = $promise->promise();
184186 }
185187
186 if ($promise === $this) {
187 return new RejectedPromise(
188 new \LogicException('Cannot resolve a promise with itself.')
189 );
190 }
191
192188 return $promise;
193189 }
194190
195 private function call(callable $callback)
196 {
191 private function call(callable $cb)
192 {
193 // Explicitly overwrite argument with null value. This ensure that this
194 // argument does not show up in the stack trace in PHP 7+ only.
195 $callback = $cb;
196 $cb = null;
197
198 // Use reflection to inspect number of arguments expected by this callback.
199 // We did some careful benchmarking here: Using reflection to avoid unneeded
200 // function arguments is actually faster than blindly passing them.
201 // Also, this helps avoiding unnecessary function arguments in the call stack
202 // if the callback creates an Exception (creating garbage cycles).
203 if (is_array($callback)) {
204 $ref = new \ReflectionMethod($callback[0], $callback[1]);
205 } elseif (is_object($callback) && !$callback instanceof \Closure) {
206 $ref = new \ReflectionMethod($callback, '__invoke');
207 } else {
208 $ref = new \ReflectionFunction($callback);
209 }
210 $args = $ref->getNumberOfParameters();
211
197212 try {
198 $callback(
199 function ($value = null) {
200 $this->resolve($value);
201 },
202 function ($reason = null) {
203 $this->reject($reason);
204 },
205 function ($update = null) {
206 $this->notify($update);
207 }
208 );
213 if ($args === 0) {
214 $callback();
215 } else {
216 // Keep references to this promise instance for the static resolve/reject functions.
217 // By using static callbacks that are not bound to this instance
218 // and passing the target promise instance by reference, we can
219 // still execute its resolving logic and still clear this
220 // reference when settling the promise. This helps avoiding
221 // garbage cycles if any callback creates an Exception.
222 // These assumptions are covered by the test suite, so if you ever feel like
223 // refactoring this, go ahead, any alternative suggestions are welcome!
224 $target =& $this;
225 $progressHandlers =& $this->progressHandlers;
226
227 $callback(
228 static function ($value = null) use (&$target) {
229 if ($target !== null) {
230 $target->settle(resolve($value));
231 $target = null;
232 }
233 },
234 static function ($reason = null) use (&$target) {
235 if ($target !== null) {
236 $target->reject($reason);
237 $target = null;
238 }
239 },
240 static function ($update = null) use (&$progressHandlers) {
241 foreach ($progressHandlers as $handler) {
242 $handler($update);
243 }
244 }
245 );
246 }
209247 } catch (\Throwable $e) {
248 $target = null;
210249 $this->reject($e);
211250 } catch (\Exception $e) {
251 $target = null;
212252 $this->reject($e);
213253 }
214254 }
44 interface PromiseInterface
55 {
66 /**
7 *
8 * The `$onProgress` argument is deprecated and should not be used anymore.
9 *
710 * @return PromiseInterface
811 */
912 public function then(callable $onFulfilled = null, callable $onRejected = null, callable $onProgress = null);
3838
3939 $deferred->progress($sentinel);
4040 }
41
42 /** @test */
43 public function shouldRejectWithoutCreatingGarbageCyclesIfCancellerRejectsWithException()
44 {
45 gc_collect_cycles();
46 $deferred = new Deferred(function ($resolve, $reject) {
47 $reject(new \Exception('foo'));
48 });
49 $deferred->promise()->cancel();
50 unset($deferred);
51
52 $this->assertSame(0, gc_collect_cycles());
53 }
54
55 /** @test */
56 public function shouldRejectWithoutCreatingGarbageCyclesIfParentCancellerRejectsWithException()
57 {
58 gc_collect_cycles();
59 $deferred = new Deferred(function ($resolve, $reject) {
60 $reject(new \Exception('foo'));
61 });
62 $deferred->promise()->then()->cancel();
63 unset($deferred);
64
65 $this->assertSame(0, gc_collect_cycles());
66 }
67
68 /** @test */
69 public function shouldRejectWithoutCreatingGarbageCyclesIfCancellerHoldsReferenceAndExplicitlyRejectWithException()
70 {
71 gc_collect_cycles();
72 $deferred = new Deferred(function () use (&$deferred) { });
73 $deferred->reject(new \Exception('foo'));
74 unset($deferred);
75
76 $this->assertSame(0, gc_collect_cycles());
77 }
78
79 /** @test */
80 public function shouldNotLeaveGarbageCyclesWhenRemovingLastReferenceToPendingDeferred()
81 {
82 gc_collect_cycles();
83 $deferred = new Deferred();
84 $deferred->promise();
85 unset($deferred);
86
87 $this->assertSame(0, gc_collect_cycles());
88 }
89
90 /** @test */
91 public function shouldNotLeaveGarbageCyclesWhenRemovingLastReferenceToPendingDeferredWithUnusedCanceller()
92 {
93 gc_collect_cycles();
94 $deferred = new Deferred(function () { });
95 $deferred->promise();
96 unset($deferred);
97
98 $this->assertSame(0, gc_collect_cycles());
99 }
100
101 /** @test */
102 public function shouldNotLeaveGarbageCyclesWhenRemovingLastReferenceToPendingDeferredWithNoopCanceller()
103 {
104 gc_collect_cycles();
105 $deferred = new Deferred(function () { });
106 $deferred->promise()->cancel();
107 unset($deferred);
108
109 $this->assertSame(0, gc_collect_cycles());
110 }
41111 }
4646
4747 return new FulfilledPromise(new FulfilledPromise());
4848 }
49
50 /** @test */
51 public function shouldNotLeaveGarbageCyclesWhenRemovingLastReferenceToFulfilledPromiseWithAlwaysFollowers()
52 {
53 gc_collect_cycles();
54 $promise = new FulfilledPromise(1);
55 $promise->always(function () {
56 throw new \RuntimeException();
57 });
58 unset($promise);
59
60 $this->assertSame(0, gc_collect_cycles());
61 }
62
63 /** @test */
64 public function shouldNotLeaveGarbageCyclesWhenRemovingLastReferenceToFulfilledPromiseWithThenFollowers()
65 {
66 gc_collect_cycles();
67 $promise = new FulfilledPromise(1);
68 $promise = $promise->then(function () {
69 throw new \RuntimeException();
70 });
71 unset($promise);
72
73 $this->assertSame(0, gc_collect_cycles());
74 }
4975 }
1313 /** @test */
1414 public function cancelShouldCallCancellerWithResolverArguments()
1515 {
16 $mock = $this->createCallableMock();
17 $mock
18 ->expects($this->once())
19 ->method('__invoke')
20 ->with($this->isType('callable'), $this->isType('callable'), $this->isType('callable'));
21
22 $adapter = $this->getPromiseTestAdapter($mock);
23
24 $adapter->promise()->cancel();
16 $args = null;
17 $adapter = $this->getPromiseTestAdapter(function ($resolve, $reject, $notify) use (&$args) {
18 $args = func_get_args();
19 });
20
21 $adapter->promise()->cancel();
22
23 $this->assertCount(3, $args);
24 $this->assertTrue(is_callable($args[0]));
25 $this->assertTrue(is_callable($args[1]));
26 $this->assertTrue(is_callable($args[2]));
27 }
28
29 /** @test */
30 public function cancelShouldCallCancellerWithoutArgumentsIfNotAccessed()
31 {
32 $args = null;
33 $adapter = $this->getPromiseTestAdapter(function () use (&$args) {
34 $args = func_num_args();
35 });
36
37 $adapter->promise()->cancel();
38
39 $this->assertSame(0, $args);
2540 }
2641
2742 /** @test */
4848 }
4949
5050 /** @test */
51 public function shouldResolveWithoutCreatingGarbageCyclesIfResolverResolvesWithException()
52 {
53 gc_collect_cycles();
54 $promise = new Promise(function ($resolve) {
55 $resolve(new \Exception('foo'));
56 });
57 unset($promise);
58
59 $this->assertSame(0, gc_collect_cycles());
60 }
61
62 /** @test */
63 public function shouldRejectWithoutCreatingGarbageCyclesIfResolverThrowsExceptionWithoutResolver()
64 {
65 gc_collect_cycles();
66 $promise = new Promise(function () {
67 throw new \Exception('foo');
68 });
69 unset($promise);
70
71 $this->assertSame(0, gc_collect_cycles());
72 }
73
74 /** @test */
75 public function shouldRejectWithoutCreatingGarbageCyclesIfResolverRejectsWithException()
76 {
77 gc_collect_cycles();
78 $promise = new Promise(function ($resolve, $reject) {
79 $reject(new \Exception('foo'));
80 });
81 unset($promise);
82
83 $this->assertSame(0, gc_collect_cycles());
84 }
85
86 /** @test */
87 public function shouldRejectWithoutCreatingGarbageCyclesIfCancellerRejectsWithException()
88 {
89 gc_collect_cycles();
90 $promise = new Promise(function ($resolve, $reject) { }, function ($resolve, $reject) {
91 $reject(new \Exception('foo'));
92 });
93 $promise->cancel();
94 unset($promise);
95
96 $this->assertSame(0, gc_collect_cycles());
97 }
98
99 /** @test */
100 public function shouldRejectWithoutCreatingGarbageCyclesIfParentCancellerRejectsWithException()
101 {
102 gc_collect_cycles();
103 $promise = new Promise(function ($resolve, $reject) { }, function ($resolve, $reject) {
104 $reject(new \Exception('foo'));
105 });
106 $promise->then()->then()->then()->cancel();
107 unset($promise);
108
109 $this->assertSame(0, gc_collect_cycles());
110 }
111
112 /** @test */
113 public function shouldRejectWithoutCreatingGarbageCyclesIfResolverThrowsException()
114 {
115 gc_collect_cycles();
116 $promise = new Promise(function ($resolve, $reject) {
117 throw new \Exception('foo');
118 });
119 unset($promise);
120
121 $this->assertSame(0, gc_collect_cycles());
122 }
123
124 /**
125 * Test that checks number of garbage cycles after throwing from a canceller
126 * that explicitly uses a reference to the promise. This is rather synthetic,
127 * actual use cases often have implicit (hidden) references which ought not
128 * to be stored in the stack trace.
129 *
130 * Reassigned arguments only show up in the stack trace in PHP 7, so we can't
131 * avoid this on legacy PHP. As an alternative, consider explicitly unsetting
132 * any references before throwing.
133 *
134 * @test
135 * @requires PHP 7
136 */
137 public function shouldRejectWithoutCreatingGarbageCyclesIfCancellerWithReferenceThrowsException()
138 {
139 gc_collect_cycles();
140 $promise = new Promise(function () {}, function () use (&$promise) {
141 throw new \Exception('foo');
142 });
143 $promise->cancel();
144 unset($promise);
145
146 $this->assertSame(0, gc_collect_cycles());
147 }
148
149 /**
150 * @test
151 * @requires PHP 7
152 * @see self::shouldRejectWithoutCreatingGarbageCyclesIfCancellerWithReferenceThrowsException
153 */
154 public function shouldRejectWithoutCreatingGarbageCyclesIfResolverWithReferenceThrowsException()
155 {
156 gc_collect_cycles();
157 $promise = new Promise(function () use (&$promise) {
158 throw new \Exception('foo');
159 });
160 unset($promise);
161
162 $this->assertSame(0, gc_collect_cycles());
163 }
164
165 /**
166 * @test
167 * @requires PHP 7
168 * @see self::shouldRejectWithoutCreatingGarbageCyclesIfCancellerWithReferenceThrowsException
169 */
170 public function shouldRejectWithoutCreatingGarbageCyclesIfCancellerHoldsReferenceAndResolverThrowsException()
171 {
172 gc_collect_cycles();
173 $promise = new Promise(function () {
174 throw new \Exception('foo');
175 }, function () use (&$promise) { });
176 unset($promise);
177
178 $this->assertSame(0, gc_collect_cycles());
179 }
180
181 /** @test */
182 public function shouldIgnoreNotifyAfterReject()
183 {
184 $promise = new Promise(function () { }, function ($resolve, $reject, $notify) {
185 $reject(new \Exception('foo'));
186 $notify(42);
187 });
188
189 $promise->then(null, null, $this->expectCallableNever());
190 $promise->cancel();
191 }
192
193
194 /** @test */
195 public function shouldNotLeaveGarbageCyclesWhenRemovingLastReferenceToPendingPromise()
196 {
197 gc_collect_cycles();
198 $promise = new Promise(function () { });
199 unset($promise);
200
201 $this->assertSame(0, gc_collect_cycles());
202 }
203
204 /** @test */
205 public function shouldNotLeaveGarbageCyclesWhenRemovingLastReferenceToPendingPromiseWithThenFollowers()
206 {
207 gc_collect_cycles();
208 $promise = new Promise(function () { });
209 $promise->then()->then()->then();
210 unset($promise);
211
212 $this->assertSame(0, gc_collect_cycles());
213 }
214
215 /** @test */
216 public function shouldNotLeaveGarbageCyclesWhenRemovingLastReferenceToPendingPromiseWithDoneFollowers()
217 {
218 gc_collect_cycles();
219 $promise = new Promise(function () { });
220 $promise->done();
221 unset($promise);
222
223 $this->assertSame(0, gc_collect_cycles());
224 }
225
226 /** @test */
227 public function shouldNotLeaveGarbageCyclesWhenRemovingLastReferenceToPendingPromiseWithOtherwiseFollowers()
228 {
229 gc_collect_cycles();
230 $promise = new Promise(function () { });
231 $promise->otherwise(function () { });
232 unset($promise);
233
234 $this->assertSame(0, gc_collect_cycles());
235 }
236
237 /** @test */
238 public function shouldNotLeaveGarbageCyclesWhenRemovingLastReferenceToPendingPromiseWithAlwaysFollowers()
239 {
240 gc_collect_cycles();
241 $promise = new Promise(function () { });
242 $promise->always(function () { });
243 unset($promise);
244
245 $this->assertSame(0, gc_collect_cycles());
246 }
247
248 /** @test */
249 public function shouldNotLeaveGarbageCyclesWhenRemovingLastReferenceToPendingPromiseWithProgressFollowers()
250 {
251 gc_collect_cycles();
252 $promise = new Promise(function () { });
253 $promise->then(null, null, function () { });
254 unset($promise);
255
256 $this->assertSame(0, gc_collect_cycles());
257 }
258
259 /** @test */
51260 public function shouldFulfillIfFullfilledWithSimplePromise()
52261 {
53262 $adapter = $this->getPromiseTestAdapter();
4646
4747 return new RejectedPromise(new RejectedPromise());
4848 }
49
50 /** @test */
51 public function shouldNotLeaveGarbageCyclesWhenRemovingLastReferenceToRejectedPromiseWithAlwaysFollowers()
52 {
53 gc_collect_cycles();
54 $promise = new RejectedPromise(1);
55 $promise->always(function () {
56 throw new \RuntimeException();
57 });
58 unset($promise);
59
60 $this->assertSame(0, gc_collect_cycles());
61 }
62
63 /** @test */
64 public function shouldNotLeaveGarbageCyclesWhenRemovingLastReferenceToRejectedPromiseWithThenFollowers()
65 {
66 gc_collect_cycles();
67 $promise = new RejectedPromise(1);
68 $promise = $promise->then(null, function () {
69 throw new \RuntimeException();
70 });
71 unset($promise);
72
73 $this->assertSame(0, gc_collect_cycles());
74 }
4975 }