@@ -146,15 +146,6 @@ private function resolver(callable $onFulfilled = null, callable $onRejected = n
146
146
};
147
147
}
148
148
149
- private function resolve ($ value = null )
150
- {
151
- if (null !== $ this ->result ) {
152
- return ;
153
- }
154
-
155
- $ this ->settle (resolve ($ value ));
156
- }
157
-
158
149
private function reject ($ reason = null )
159
150
{
160
151
if (null !== $ this ->result ) {
@@ -228,23 +219,75 @@ private function call(callable $callback)
228
219
if ($ args === 0 ) {
229
220
$ callback ();
230
221
} else {
222
+ // keep a reference to this promise instance for the static resolve/reject functions.
223
+ // see also resolveFunction() and rejectFunction() for more details.
224
+ $ target =& $ this ;
225
+
231
226
$ callback (
232
- function ($ value = null ) {
233
- $ this ->resolve ($ value );
234
- },
235
- function ($ reason = null ) {
236
- $ this ->reject ($ reason );
237
- },
238
- self ::notifier ($ this ->progressHandlers )
227
+ self ::resolveFunction ($ target ),
228
+ self ::rejectFunction ($ target ),
229
+ self ::notifyFunction ($ this ->progressHandlers )
239
230
);
240
231
}
241
232
} catch (\Throwable $ e ) {
233
+ $ target = null ;
242
234
$ this ->reject ($ e );
243
235
} catch (\Exception $ e ) {
236
+ $ target = null ;
244
237
$ this ->reject ($ e );
245
238
}
246
239
}
247
240
241
+ /**
242
+ * Creates a static resolver callback that is not bound to a promise instance.
243
+ *
244
+ * Moving the closure creation to a static method allows us to create a
245
+ * callback that is not bound to a promise instance. By passing the target
246
+ * promise instance by reference, we can still execute its resolving logic
247
+ * and still clear this reference when settling the promise. This helps
248
+ * avoiding garbage cycles if any callback creates an Exception.
249
+ *
250
+ * These assumptions are covered by the test suite, so if you ever feel like
251
+ * refactoring this, go ahead, any alternative suggestions are welcome!
252
+ *
253
+ * @param Promise $target
254
+ * @return callable
255
+ */
256
+ private static function resolveFunction (self &$ target )
257
+ {
258
+ return function ($ value = null ) use (&$ target ) {
259
+ if ($ target !== null ) {
260
+ $ target ->settle (resolve ($ value ));
261
+ $ target = null ;
262
+ }
263
+ };
264
+ }
265
+
266
+ /**
267
+ * Creates a static rejection callback that is not bound to a promise instance.
268
+ *
269
+ * Moving the closure creation to a static method allows us to create a
270
+ * callback that is not bound to a promise instance. By passing the target
271
+ * promise instance by reference, we can still execute its rejection logic
272
+ * and still clear this reference when settling the promise. This helps
273
+ * avoiding garbage cycles if any callback creates an Exception.
274
+ *
275
+ * These assumptions are covered by the test suite, so if you ever feel like
276
+ * refactoring this, go ahead, any alternative suggestions are welcome!
277
+ *
278
+ * @param Promise $target
279
+ * @return callable
280
+ */
281
+ private static function rejectFunction (self &$ target )
282
+ {
283
+ return function ($ reason = null ) use (&$ target ) {
284
+ if ($ target !== null ) {
285
+ $ target ->reject ($ reason );
286
+ $ target = null ;
287
+ }
288
+ };
289
+ }
290
+
248
291
/**
249
292
* Creates a static progress callback that is not bound to a promise instance.
250
293
*
@@ -260,7 +303,7 @@ function ($reason = null) {
260
303
* @param array $progressHandlers
261
304
* @return callable
262
305
*/
263
- private static function notifier (&$ progressHandlers )
306
+ private static function notifyFunction (&$ progressHandlers )
264
307
{
265
308
return function ($ update = null ) use (&$ progressHandlers ) {
266
309
foreach ($ progressHandlers as $ handler ) {
0 commit comments