@@ -14,7 +14,13 @@ final class Promise implements PromiseInterface
14
14
public function __construct (callable $ resolver , callable $ canceller = null )
15
15
{
16
16
$ this ->canceller = $ canceller ;
17
- $ this ->call ($ resolver );
17
+
18
+ // Explicitly overwrite arguments with null values before invoking
19
+ // resolver function. This ensure that these arguments do not show up
20
+ // in the stack trace in PHP 7+ only.
21
+ $ cb = $ resolver ;
22
+ $ resolver = $ canceller = null ;
23
+ $ this ->call ($ cb );
18
24
}
19
25
20
26
public function then (callable $ onFulfilled = null , callable $ onRejected = null ): PromiseInterface
@@ -27,15 +33,26 @@ public function then(callable $onFulfilled = null, callable $onRejected = null):
27
33
return new static ($ this ->resolver ($ onFulfilled , $ onRejected ));
28
34
}
29
35
30
- $ this ->requiredCancelRequests ++;
36
+ // This promise has a canceller, so we create a new child promise which
37
+ // has a canceller that invokes the parent canceller if all other
38
+ // followers are also cancelled. We keep a reference to this promise
39
+ // instance for the static canceller function and clear this to avoid
40
+ // keeping a cyclic reference between parent and follower.
41
+ $ parent = $ this ;
42
+ ++$ parent ->requiredCancelRequests ;
43
+
44
+ return new static (
45
+ $ this ->resolver ($ onFulfilled , $ onRejected ),
46
+ static function () use (&$ parent ) {
47
+ --$ parent ->requiredCancelRequests ;
31
48
32
- return new static ($ this ->resolver ($ onFulfilled , $ onRejected ), function () {
33
- $ this ->requiredCancelRequests --;
49
+ if ($ parent ->requiredCancelRequests <= 0 ) {
50
+ $ parent ->cancel ();
51
+ }
34
52
35
- if ($ this ->requiredCancelRequests <= 0 ) {
36
- $ this ->cancel ();
53
+ $ parent = null ;
37
54
}
38
- } );
55
+ );
39
56
}
40
57
41
58
public function done (callable $ onFulfilled = null , callable $ onRejected = null ): void
@@ -45,15 +62,15 @@ public function done(callable $onFulfilled = null, callable $onRejected = null):
45
62
return ;
46
63
}
47
64
48
- $ this ->handlers [] = function (PromiseInterface $ promise ) use ($ onFulfilled , $ onRejected ) {
65
+ $ this ->handlers [] = static function (PromiseInterface $ promise ) use ($ onFulfilled , $ onRejected ) {
49
66
$ promise
50
67
->done ($ onFulfilled , $ onRejected );
51
68
};
52
69
}
53
70
54
71
public function otherwise (callable $ onRejected ): PromiseInterface
55
72
{
56
- return $ this ->then (null , function ($ reason ) use ($ onRejected ) {
73
+ return $ this ->then (null , static function ($ reason ) use ($ onRejected ) {
57
74
if (!_checkTypehint ($ onRejected , $ reason )) {
58
75
return new RejectedPromise ($ reason );
59
76
}
@@ -64,11 +81,11 @@ public function otherwise(callable $onRejected): PromiseInterface
64
81
65
82
public function always (callable $ onFulfilledOrRejected ): PromiseInterface
66
83
{
67
- return $ this ->then (function ($ value ) use ($ onFulfilledOrRejected ) {
84
+ return $ this ->then (static function ($ value ) use ($ onFulfilledOrRejected ) {
68
85
return resolve ($ onFulfilledOrRejected ())->then (function () use ($ value ) {
69
86
return $ value ;
70
87
});
71
- }, function ($ reason ) use ($ onFulfilledOrRejected ) {
88
+ }, static function ($ reason ) use ($ onFulfilledOrRejected ) {
72
89
return resolve ($ onFulfilledOrRejected ())->then (function () use ($ reason ) {
73
90
return new RejectedPromise ($ reason );
74
91
});
@@ -113,23 +130,14 @@ public function cancel(): void
113
130
private function resolver (callable $ onFulfilled = null , callable $ onRejected = null ): callable
114
131
{
115
132
return function ($ resolve , $ reject ) use ($ onFulfilled , $ onRejected ) {
116
- $ this ->handlers [] = function (PromiseInterface $ promise ) use ($ onFulfilled , $ onRejected , $ resolve , $ reject ) {
133
+ $ this ->handlers [] = static function (PromiseInterface $ promise ) use ($ onFulfilled , $ onRejected , $ resolve , $ reject ) {
117
134
$ promise
118
135
->then ($ onFulfilled , $ onRejected )
119
136
->done ($ resolve , $ reject );
120
137
};
121
138
};
122
139
}
123
140
124
- private function resolve ($ value = null ): void
125
- {
126
- if (null !== $ this ->result ) {
127
- return ;
128
- }
129
-
130
- $ this ->settle (resolve ($ value ));
131
- }
132
-
133
141
private function reject (\Throwable $ reason ): void
134
142
{
135
143
if (null !== $ this ->result ) {
@@ -175,8 +183,13 @@ private function unwrap($promise): PromiseInterface
175
183
return $ promise ;
176
184
}
177
185
178
- private function call (callable $ callback ): void
186
+ private function call (callable $ cb ): void
179
187
{
188
+ // Explicitly overwrite argument with null value. This ensure that this
189
+ // argument does not show up in the stack trace in PHP 7+ only.
190
+ $ callback = $ cb ;
191
+ $ cb = null ;
192
+
180
193
// Use reflection to inspect number of arguments expected by this callback.
181
194
// We did some careful benchmarking here: Using reflection to avoid unneeded
182
195
// function arguments is actually faster than blindly passing them.
@@ -195,16 +208,33 @@ private function call(callable $callback): void
195
208
if ($ args === 0 ) {
196
209
$ callback ();
197
210
} else {
211
+ // Keep references to this promise instance for the static resolve/reject functions.
212
+ // By using static callbacks that are not bound to this instance
213
+ // and passing the target promise instance by reference, we can
214
+ // still execute its resolving logic and still clear this
215
+ // reference when settling the promise. This helps avoiding
216
+ // garbage cycles if any callback creates an Exception.
217
+ // These assumptions are covered by the test suite, so if you ever feel like
218
+ // refactoring this, go ahead, any alternative suggestions are welcome!
219
+ $ target =& $ this ;
220
+
198
221
$ callback (
199
- function ($ value = null ) {
200
- $ this ->resolve ($ value );
222
+ static function ($ value = null ) use (&$ target ) {
223
+ if ($ target !== null ) {
224
+ $ target ->settle (resolve ($ value ));
225
+ $ target = null ;
226
+ }
201
227
},
202
- function (\Throwable $ reason ) {
203
- $ this ->reject ($ reason );
228
+ static function (\Throwable $ reason ) use (&$ target ) {
229
+ if ($ target !== null ) {
230
+ $ target ->reject ($ reason );
231
+ $ target = null ;
232
+ }
204
233
}
205
234
);
206
235
}
207
236
} catch (\Throwable $ e ) {
237
+ $ target = null ;
208
238
$ this ->reject ($ e );
209
239
}
210
240
}
0 commit comments