Skip to content

Commit 8b56118

Browse files
committed
Merge branch 'master' into enforce-exception-reasons
2 parents 57e873c + 9dcffcc commit 8b56118

14 files changed

+328
-272
lines changed

.travis.yml

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,16 @@ php:
66
- 5.6
77
- 7.0
88
- 7.1
9-
- nightly
10-
- hhvm
9+
- nightly # ignore errors, see below
10+
- hhvm # ignore errors, see below
11+
12+
# lock distro so new future defaults will not break the build
13+
dist: trusty
14+
15+
matrix:
16+
allow_failures:
17+
- php: hhvm
18+
- php: nightly
1119

1220
install:
1321
- composer install

README.md

Lines changed: 8 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ Table of Contents
1717
1. [Introduction](#introduction)
1818
2. [Concepts](#concepts)
1919
* [Deferred](#deferred)
20-
* [Promise](#promise)
20+
* [Promise](#promise-1)
2121
3. [API](#api)
2222
* [Deferred](#deferred-1)
2323
* [Deferred::promise()](#deferredpromise)
@@ -29,10 +29,9 @@ Table of Contents
2929
* [PromiseInterface::otherwise()](#promiseinterfaceotherwise)
3030
* [PromiseInterface::always()](#promiseinterfacealways)
3131
* [PromiseInterface::cancel()](#promiseinterfacecancel)
32-
* [Promise](#promise-1)
32+
* [Promise](#promise-2)
3333
* [FulfilledPromise](#fulfilledpromise)
3434
* [RejectedPromise](#rejectedpromise)
35-
* [LazyPromise](#lazypromise)
3635
* [Functions](#functions)
3736
* [resolve()](#resolve)
3837
* [reject()](#reject)
@@ -102,7 +101,7 @@ The `promise` method returns the promise of the deferred.
102101
The `resolve` and `reject` methods control the state of the deferred.
103102

104103
The constructor of the `Deferred` accepts an optional `$canceller` argument.
105-
See [Promise](#promise-1) for more information.
104+
See [Promise](#promise-2) for more information.
106105

107106
#### Deferred::promise()
108107

@@ -150,10 +149,9 @@ Neither its state nor its result (or error) can be modified.
150149

151150
#### Implementations
152151

153-
* [Promise](#promise-1)
152+
* [Promise](#promise-2)
154153
* [FulfilledPromise](#fulfilledpromise)
155154
* [RejectedPromise](#rejectedpromise)
156-
* [LazyPromise](#lazypromise)
157155

158156
#### PromiseInterface::then()
159157

@@ -200,8 +198,8 @@ $promise->done(callable $onFulfilled = null, callable $onRejected = null);
200198
Consumes the promise's ultimate value if the promise fulfills, or handles the
201199
ultimate error.
202200

203-
It will cause a fatal error if either `$onFulfilled` or `$onRejected` throw or
204-
return a rejected promise.
201+
It will cause a fatal error (`E_USER_ERROR`) if either `$onFulfilled` or
202+
`$onRejected` throw or return a rejected promise.
205203

206204
Since the purpose of `done()` is consumption rather than transformation,
207205
`done()` always returns `null`.
@@ -357,27 +355,6 @@ $promise = React\Promise\RejectedPromise($reason);
357355

358356
Note, that `$reason` **must** be a `\Throwable` or `\Exception`.
359357

360-
### LazyPromise
361-
362-
Creates a promise which will be lazily initialized by `$factory` once a consumer
363-
calls the `then()` method.
364-
365-
```php
366-
$factory = function () {
367-
$deferred = new React\Promise\Deferred();
368-
369-
// Do some heavy stuff here and resolve the deferred once completed
370-
371-
return $deferred->promise();
372-
};
373-
374-
$promise = new React\Promise\LazyPromise($factory);
375-
376-
// $factory will only be executed once we call then()
377-
$promise->then(function ($value) {
378-
});
379-
```
380-
381358
### Functions
382359

383360
Useful functions for creating, joining, mapping and reducing collections of
@@ -660,8 +637,8 @@ by the promise machinery and used to reject the promise returned by `then()`.
660637

661638
Calling `done()` transfers all responsibility for errors to your code. If an
662639
error (either a thrown exception or returned rejection) escapes the
663-
`$onFulfilled` or `$onRejected` callbacks you provide to done, it will be
664-
rethrown in an uncatchable way causing a fatal error.
640+
`$onFulfilled` or `$onRejected` callbacks you provide to `done()`, it will cause
641+
a fatal error.
665642

666643
```php
667644
function getJsonResult()

src/FulfilledPromise.php

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,13 @@ public function done(callable $onFulfilled = null, callable $onRejected = null)
4141
}
4242

4343
enqueue(function () use ($onFulfilled) {
44-
$result = $onFulfilled($this->value);
44+
try {
45+
$result = $onFulfilled($this->value);
46+
} catch (\Throwable $exception) {
47+
return fatalError($exception);
48+
} catch (\Exception $exception) {
49+
return fatalError($exception);
50+
}
4551

4652
if ($result instanceof PromiseInterface) {
4753
$result->done();

src/LazyPromise.php

Lines changed: 0 additions & 61 deletions
This file was deleted.

src/Promise.php

Lines changed: 48 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ final class Promise implements PromiseInterface
99

1010
private $handlers = [];
1111

12-
private $remainingCancelRequests = 0;
12+
private $requiredCancelRequests = 0;
1313

1414
public function __construct(callable $resolver, callable $canceller = null)
1515
{
@@ -27,14 +27,14 @@ public function then(callable $onFulfilled = null, callable $onRejected = null)
2727
return new static($this->resolver($onFulfilled, $onRejected));
2828
}
2929

30-
$this->remainingCancelRequests++;
30+
$this->requiredCancelRequests++;
3131

3232
return new static($this->resolver($onFulfilled, $onRejected), function () {
33-
if (--$this->remainingCancelRequests > 0) {
34-
return;
35-
}
33+
$this->requiredCancelRequests--;
3634

37-
$this->cancel();
35+
if ($this->requiredCancelRequests <= 0) {
36+
$this->cancel();
37+
}
3838
});
3939
}
4040

@@ -76,14 +76,37 @@ public function always(callable $onFulfilledOrRejected)
7676

7777
public function cancel()
7878
{
79-
if (null === $this->canceller) {
80-
return;
81-
}
82-
8379
$canceller = $this->canceller;
8480
$this->canceller = null;
8581

86-
$this->call($canceller);
82+
$parentCanceller = null;
83+
84+
if (null !== $this->result) {
85+
// Go up the promise chain and reach the top most promise which is
86+
// itself not following another promise
87+
$root = $this->unwrap($this->result);
88+
89+
// Return if the root promise is already resolved or a
90+
// FulfilledPromise or RejectedPromise
91+
if (!$root instanceof self || null !== $root->result) {
92+
return;
93+
}
94+
95+
$root->requiredCancelRequests--;
96+
97+
if ($root->requiredCancelRequests <= 0) {
98+
$parentCanceller = [$root, 'cancel'];
99+
}
100+
}
101+
102+
if (null !== $canceller) {
103+
$this->call($canceller);
104+
}
105+
106+
// For BC, we call the parent canceller after our own canceller
107+
if ($parentCanceller) {
108+
$parentCanceller();
109+
}
87110
}
88111

89112
private function resolver(callable $onFulfilled = null, callable $onRejected = null)
@@ -119,10 +142,22 @@ private function settle(PromiseInterface $result)
119142
{
120143
$result = $this->unwrap($result);
121144

145+
if ($result === $this) {
146+
$result = new RejectedPromise(
147+
new \LogicException('Cannot resolve a promise with itself.')
148+
);
149+
}
150+
151+
if ($result instanceof self) {
152+
$result->requiredCancelRequests++;
153+
} else {
154+
// Unset canceller only when not following a pending promise
155+
$this->canceller = null;
156+
}
157+
122158
$handlers = $this->handlers;
123159

124160
$this->handlers = [];
125-
$this->canceller = null;
126161
$this->result = $result;
127162

128163
foreach ($handlers as $handler) {
@@ -132,25 +167,8 @@ private function settle(PromiseInterface $result)
132167

133168
private function unwrap($promise)
134169
{
135-
$promise = $this->extract($promise);
136-
137170
while ($promise instanceof self && null !== $promise->result) {
138-
$promise = $this->extract($promise->result);
139-
}
140-
141-
return $promise;
142-
}
143-
144-
private function extract($promise)
145-
{
146-
if ($promise instanceof LazyPromise) {
147-
$promise = $promise->promise();
148-
}
149-
150-
if ($promise === $this) {
151-
return new RejectedPromise(
152-
new \LogicException('Cannot resolve a promise with itself.')
153-
);
171+
$promise = $promise->result;
154172
}
155173

156174
return $promise;

src/RejectedPromise.php

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -40,13 +40,19 @@ public function done(callable $onFulfilled = null, callable $onRejected = null)
4040
{
4141
enqueue(function () use ($onRejected) {
4242
if (null === $onRejected) {
43-
throw $this->reason;
43+
return fatalError($this->reason);
4444
}
4545

46-
$result = $onRejected($this->reason);
46+
try {
47+
$result = $onRejected($this->reason);
48+
} catch (\Throwable $exception) {
49+
return fatalError($exception);
50+
} catch (\Exception $exception) {
51+
return fatalError($exception);
52+
}
4753

4854
if ($result instanceof self) {
49-
throw $result->reason;
55+
return fatalError($result->reason);
5056
}
5157

5258
if ($result instanceof PromiseInterface) {

src/functions.php

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -201,6 +201,22 @@ function enqueue(callable $task)
201201
$queue->enqueue($task);
202202
}
203203

204+
/**
205+
* @internal
206+
*/
207+
function fatalError($error)
208+
{
209+
try {
210+
trigger_error($error, E_USER_ERROR);
211+
} catch (\Throwable $e) {
212+
set_error_handler(null);
213+
trigger_error($error, E_USER_ERROR);
214+
} catch (\Exception $e) {
215+
set_error_handler(null);
216+
trigger_error($error, E_USER_ERROR);
217+
}
218+
}
219+
204220
/**
205221
* @internal
206222
*/

tests/ErrorCollector.php

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
<?php
2+
3+
namespace React\Promise;
4+
5+
final class ErrorCollector
6+
{
7+
private $errors = [];
8+
9+
public function start()
10+
{
11+
$errors = [];
12+
13+
set_error_handler(function ($errno, $errstr, $errfile, $errline, $errcontext) use (&$errors) {
14+
$errors[] = compact('errno', 'errstr', 'errfile', 'errline', 'errcontext');
15+
});
16+
17+
$this->errors = &$errors;
18+
}
19+
20+
public function stop()
21+
{
22+
$errors = $this->errors;
23+
$this->errors = [];
24+
25+
restore_error_handler();
26+
27+
return $errors;
28+
}
29+
}

0 commit comments

Comments
 (0)