1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
namespace Dazzle\Promise\Partial; |
4
|
|
|
|
5
|
|
|
use Dazzle\Promise\Helper\CancellationQueue; |
6
|
|
|
use Dazzle\Promise\Promise; |
7
|
|
|
use Dazzle\Promise\PromiseCancelled; |
8
|
|
|
use Dazzle\Promise\PromiseFulfilled; |
9
|
|
|
use Dazzle\Promise\PromiseInterface; |
10
|
|
|
use Dazzle\Promise\PromiseRejected; |
11
|
|
|
use Dazzle\Throwable\Exception\Runtime\UnderflowException; |
12
|
|
|
|
13
|
|
|
trait PromiseTrait |
14
|
|
|
{ |
15
|
|
|
/** |
16
|
|
|
* Resolve Promise or value. |
17
|
|
|
* |
18
|
|
|
* @param PromiseInterface|mixed $promiseOrValue |
19
|
|
|
* @return PromiseInterface |
20
|
|
|
* @resolves mixed |
21
|
|
|
* @rejects Error|Exception|string|null |
22
|
|
|
* @cancels Error|Exception|string|null |
23
|
|
|
*/ |
24
|
302 |
|
public static function doResolve($promiseOrValue = null) |
25
|
|
|
{ |
26
|
302 |
|
if (!$promiseOrValue instanceof PromiseInterface) |
27
|
|
|
{ |
28
|
302 |
|
return new PromiseFulfilled($promiseOrValue); |
29
|
|
|
} |
30
|
|
|
|
31
|
198 |
|
return $promiseOrValue; |
32
|
|
|
} |
33
|
|
|
|
34
|
|
|
/** |
35
|
|
|
* Reject Promise or value. |
36
|
|
|
* |
37
|
|
|
* @param PromiseInterface|mixed $promiseOrValue |
38
|
|
|
* @return PromiseInterface |
39
|
|
|
* @rejects Error|Exception|string|null |
40
|
|
|
*/ |
41
|
143 |
View Code Duplication |
public static function doReject($promiseOrValue = null) |
|
|
|
|
42
|
|
|
{ |
43
|
143 |
|
if (!$promiseOrValue instanceof PromiseInterface) |
44
|
|
|
{ |
45
|
139 |
|
return new PromiseRejected($promiseOrValue); |
46
|
|
|
} |
47
|
|
|
|
48
|
|
|
return self::doResolve($promiseOrValue)->then(function($value) { |
49
|
3 |
|
return new PromiseRejected($value); |
50
|
6 |
|
}); |
51
|
|
|
} |
52
|
|
|
|
53
|
|
|
/** |
54
|
|
|
* Cancel Promise or value. |
55
|
|
|
* |
56
|
|
|
* @param PromiseInterface|mixed $promiseOrValue |
57
|
|
|
* @return PromiseInterface |
58
|
|
|
* @cancels Error|Exception|string|null |
59
|
|
|
*/ |
60
|
127 |
View Code Duplication |
public static function doCancel($promiseOrValue = null) |
|
|
|
|
61
|
|
|
{ |
62
|
127 |
|
if (!$promiseOrValue instanceof PromiseInterface) |
63
|
|
|
{ |
64
|
127 |
|
return new PromiseCancelled($promiseOrValue); |
65
|
|
|
} |
66
|
|
|
|
67
|
4 |
|
return self::doResolve($promiseOrValue) |
68
|
4 |
|
->then( |
69
|
|
|
function($value) { |
70
|
2 |
|
return new PromiseCancelled($value); |
71
|
4 |
|
}, |
72
|
|
|
function($value) { |
73
|
2 |
|
return new PromiseCancelled($value); |
74
|
4 |
|
} |
75
|
|
|
); |
76
|
|
|
} |
77
|
|
|
|
78
|
|
|
/** |
79
|
|
|
* Return Promise that will resolve only once all the items in $promisesOrValues have resolved. |
80
|
|
|
* |
81
|
|
|
* Return Promise that will resolve only once all the items in $promisesOrValues have resolved. The resolution |
82
|
|
|
* value of the returned promise will be an array containing the resolution values of each of the items in |
83
|
|
|
* $promisesOrValues. |
84
|
|
|
* |
85
|
|
|
* @param PromiseInterface[]|mixed[] $promisesOrValues |
86
|
|
|
* @return PromiseInterface |
87
|
|
|
* @resolves mixed |
88
|
|
|
* @rejects Error|Exception|string|null |
89
|
|
|
* @cancels Error|Exception|string|null |
90
|
|
|
*/ |
91
|
4 |
|
public static function all($promisesOrValues) |
92
|
|
|
{ |
93
|
|
|
return self::map($promisesOrValues, function($val) { |
94
|
3 |
|
return $val; |
95
|
4 |
|
}); |
96
|
|
|
} |
97
|
|
|
|
98
|
|
|
/** |
99
|
|
|
* Initiate a competitive race that allows one winner. |
100
|
|
|
* |
101
|
|
|
* Initiate a competitive race that allows one winner. Returns a promise which is resolved in the same way |
102
|
|
|
* the first settled promise resolves. |
103
|
|
|
* |
104
|
|
|
* @param PromiseInterface[]|mixed[] $promisesOrValues |
105
|
|
|
* @return PromiseInterface |
106
|
|
|
* @resolves mixed |
107
|
|
|
* @rejects Error|Exception|string|null |
108
|
|
|
* @cancels Error|Exception|string|null |
109
|
|
|
*/ |
110
|
7 |
|
public static function race($promisesOrValues) |
111
|
|
|
{ |
112
|
7 |
|
$cancellationQueue = new CancellationQueue(); |
113
|
|
|
|
114
|
|
|
return new Promise(function($resolve, $reject, $cancel) use($promisesOrValues, $cancellationQueue) { |
115
|
7 |
|
self::doResolve($promisesOrValues) |
116
|
|
|
->done(function($array) use($resolve, $reject, $cancel, $cancellationQueue) { |
117
|
7 |
|
if (!is_array($array) || !$array) |
|
|
|
|
118
|
|
|
{ |
119
|
1 |
|
$resolve(); |
120
|
1 |
|
return; |
121
|
|
|
} |
122
|
|
|
|
123
|
|
|
$fulfiller = function($value) use($resolve, $cancellationQueue) { |
124
|
3 |
|
$resolve($value); |
125
|
3 |
|
$cancellationQueue(); |
126
|
6 |
|
}; |
127
|
|
|
|
128
|
|
|
$rejecter = function($reason) use($reject, $cancellationQueue) { |
129
|
2 |
|
$reject($reason); |
130
|
2 |
|
$cancellationQueue(); |
131
|
6 |
|
}; |
132
|
|
|
|
133
|
6 |
|
foreach ($array as $promiseOrValue) |
134
|
|
|
{ |
135
|
6 |
|
$cancellationQueue->enqueue($promiseOrValue); |
136
|
|
|
|
137
|
6 |
|
self::doResolve($promiseOrValue) |
138
|
6 |
|
->done($fulfiller, $rejecter, $cancel); |
139
|
|
|
} |
140
|
7 |
|
}, $reject, $cancel); |
141
|
7 |
|
}, $cancellationQueue); |
142
|
|
|
} |
143
|
|
|
|
144
|
|
|
/** |
145
|
|
|
* Return a promise that will resolve when any one of the items in $promisesOrValues resolves. |
146
|
|
|
* |
147
|
|
|
* Return a promise that will resolve when any one of the items in $promisesOrValues resolves. The resolution value |
148
|
|
|
* of the returned promise will be the resolution value of the triggering item. |
149
|
|
|
* |
150
|
|
|
* @param PromiseInterface[]|mixed[] $promisesOrValues |
151
|
|
|
* @return PromiseInterface |
152
|
|
|
* @resolves mixed |
153
|
|
|
* @rejects Error|Exception|string|null |
154
|
|
|
* @cancels Error|Exception|string|null |
155
|
|
|
*/ |
156
|
7 |
|
public static function any($promisesOrValues) |
157
|
|
|
{ |
158
|
7 |
|
return self::some($promisesOrValues, 1) |
159
|
|
|
->then(function($val) { |
160
|
4 |
|
return array_shift($val); |
161
|
7 |
|
}); |
162
|
|
|
} |
163
|
|
|
|
164
|
|
|
/** |
165
|
|
|
* Return Promise that will resolve when $howMany of the supplied items in $promisesOrValues resolve. |
166
|
|
|
* |
167
|
|
|
* Return Promise that will resolve when $howMany of the supplied items in $promisesOrValues resolve. The resolution |
168
|
|
|
* value of the returned promise will be an array of length $howMany containing the resolution values of |
169
|
|
|
* the triggering items. |
170
|
|
|
* |
171
|
|
|
* @param PromiseInterface[]|mixed[] $promisesOrValues |
172
|
|
|
* @param int $howMany |
173
|
|
|
* @return PromiseInterface |
174
|
|
|
* @resolves mixed |
175
|
|
|
* @rejects Error|Exception|string|null |
176
|
|
|
* @cancels Error|Exception|string|null |
177
|
|
|
*/ |
178
|
18 |
|
public static function some($promisesOrValues, $howMany) |
179
|
|
|
{ |
180
|
18 |
|
$cancellationQueue = new CancellationQueue(); |
181
|
|
|
|
182
|
|
|
return new Promise(function($resolve, $reject, $cancel) use($promisesOrValues, $howMany, $cancellationQueue) { |
183
|
18 |
|
self::doResolve($promisesOrValues) |
184
|
|
|
->done(function($array) use($resolve, $reject, $cancel, $howMany, $cancellationQueue) { |
185
|
18 |
|
if (!is_array($array) || $howMany < 1) |
186
|
|
|
{ |
187
|
1 |
|
$resolve([]); |
188
|
1 |
|
return; |
189
|
|
|
} |
190
|
|
|
|
191
|
17 |
|
$len = count($array); |
192
|
|
|
|
193
|
17 |
|
if ($len < $howMany) |
194
|
|
|
{ |
195
|
3 |
|
throw new UnderflowException( |
196
|
3 |
|
sprintf('Input array must contain at least %d items but contains only %s items.', $howMany, $len) |
197
|
|
|
); |
198
|
|
|
} |
199
|
|
|
|
200
|
14 |
|
$toResolve = $howMany; |
201
|
14 |
|
$toReject = ($len - $toResolve) + 1; |
202
|
14 |
|
$values = []; |
203
|
14 |
|
$reasons = []; |
204
|
|
|
|
205
|
14 |
|
foreach ($array as $i=>$promiseOrValue) |
206
|
|
|
{ |
207
|
|
View Code Duplication |
$fulfiller = function($val) use($i, &$values, &$toResolve, $toReject, $resolve, $cancellationQueue) { |
|
|
|
|
208
|
10 |
|
if ($toResolve < 1 || $toReject < 1) |
209
|
|
|
{ |
210
|
5 |
|
return; |
211
|
|
|
} |
212
|
|
|
|
213
|
10 |
|
$values[$i] = $val; |
214
|
|
|
|
215
|
10 |
|
if (0 === --$toResolve) |
216
|
|
|
{ |
217
|
9 |
|
$resolve($values); |
218
|
9 |
|
$cancellationQueue(); |
219
|
|
|
} |
220
|
14 |
|
}; |
221
|
|
|
|
222
|
|
View Code Duplication |
$rejecter = function($reason) use($i, &$reasons, &$toReject, $toResolve, $reject, $cancellationQueue) { |
|
|
|
|
223
|
4 |
|
if ($toResolve < 1 || $toReject < 1) |
224
|
|
|
{ |
225
|
1 |
|
return; |
226
|
|
|
} |
227
|
|
|
|
228
|
4 |
|
$reasons[$i] = $reason; |
229
|
|
|
|
230
|
4 |
|
if (0 === --$toReject) |
231
|
|
|
{ |
232
|
3 |
|
$reject($reasons); |
233
|
3 |
|
$cancellationQueue(); |
234
|
|
|
} |
235
|
14 |
|
}; |
236
|
|
|
|
237
|
14 |
|
$canceller = $cancel; |
238
|
|
|
|
239
|
14 |
|
$cancellationQueue->enqueue($promiseOrValue); |
240
|
|
|
|
241
|
14 |
|
self::doResolve($promiseOrValue) |
242
|
14 |
|
->done($fulfiller, $rejecter, $canceller); |
243
|
|
|
} |
244
|
18 |
|
}, $reject, $cancel); |
245
|
18 |
|
}, $cancellationQueue); |
246
|
|
|
} |
247
|
|
|
|
248
|
|
|
/** |
249
|
|
|
* Map promises and/or values using specified $mapFunc. |
250
|
|
|
* |
251
|
|
|
* @see array_map |
252
|
|
|
* |
253
|
|
|
* @param PromiseInterface[]|mixed[] $promisesOrValues |
254
|
|
|
* @param callable $mapFunc |
255
|
|
|
* @return PromiseInterface |
256
|
|
|
* @resolves mixed |
257
|
|
|
* @rejects Error|Exception|string|null |
258
|
|
|
* @cancels Error|Exception|string|null |
259
|
|
|
*/ |
260
|
10 |
|
public static function map($promisesOrValues, callable $mapFunc) |
261
|
|
|
{ |
262
|
10 |
|
$cancellationQueue = new CancellationQueue(); |
263
|
|
|
|
264
|
|
|
return new Promise(function($resolve, $reject, $cancel) use($promisesOrValues, $mapFunc, $cancellationQueue) { |
265
|
10 |
|
self::doResolve($promisesOrValues) |
266
|
|
|
->done(function($array) use($resolve, $reject, $cancel, $mapFunc, $cancellationQueue) { |
267
|
10 |
|
if (!is_array($array) || !$array) |
|
|
|
|
268
|
|
|
{ |
269
|
1 |
|
$resolve([]); |
270
|
1 |
|
return; |
271
|
|
|
} |
272
|
|
|
|
273
|
9 |
|
$toResolve = count($array); |
274
|
9 |
|
$values = []; |
275
|
|
|
|
276
|
9 |
|
foreach ($array as $i=>$promiseOrValue) |
277
|
|
|
{ |
278
|
9 |
|
$cancellationQueue->enqueue($promiseOrValue); |
279
|
|
|
|
280
|
9 |
|
self::doResolve($promiseOrValue) |
281
|
9 |
|
->then($mapFunc) |
282
|
9 |
|
->done( |
283
|
|
|
function($mapped) use($i, &$values, &$toResolve, $resolve) { |
284
|
8 |
|
$values[$i] = $mapped; |
285
|
8 |
|
if (0 === --$toResolve) |
286
|
|
|
{ |
287
|
6 |
|
$resolve($values); |
288
|
|
|
} |
289
|
9 |
|
}, |
290
|
9 |
|
$reject, |
291
|
9 |
|
$cancel |
292
|
|
|
); |
293
|
|
|
} |
294
|
10 |
|
}, $reject, $cancel); |
295
|
10 |
|
}, $cancellationQueue); |
296
|
|
|
} |
297
|
|
|
|
298
|
|
|
/** |
299
|
|
|
* Reduce Promises and/or values using $reduceFunc with $initialValue being Promise or primitive value. |
300
|
|
|
* |
301
|
|
|
* @see array_reduce |
302
|
|
|
* |
303
|
|
|
* @param PromiseInterface[]|mixed[] $promisesOrValues |
304
|
|
|
* @param callable $reduceFunc |
305
|
|
|
* @param PromiseInterface|mixed|null $initialValue |
306
|
|
|
* @return PromiseInterface |
307
|
|
|
* @resolves mixed |
308
|
|
|
* @rejects Error|Exception|string|null |
309
|
|
|
* @cancels Error|Exception|string|null |
310
|
|
|
*/ |
311
|
15 |
|
public static function reduce($promisesOrValues, callable $reduceFunc, $initialValue = null) |
312
|
|
|
{ |
313
|
15 |
|
$cancellationQueue = new CancellationQueue(); |
314
|
|
|
|
315
|
|
|
return new Promise(function($resolve, $reject, $cancel) use($promisesOrValues, $reduceFunc, $initialValue, $cancellationQueue) { |
316
|
15 |
|
self::doResolve($promisesOrValues) |
317
|
|
|
->done(function($array) use($reduceFunc, $initialValue, $resolve, $reject, $cancel, $cancellationQueue) { |
318
|
15 |
|
if (!is_array($array)) |
319
|
|
|
{ |
320
|
|
|
$array = []; |
321
|
|
|
} |
322
|
|
|
|
323
|
15 |
|
$total = count($array); |
324
|
15 |
|
$i = 0; |
325
|
|
|
|
326
|
|
|
// Wrap the supplied $reduceFunc with one that handles promises and then delegates to the supplied. |
327
|
|
|
// |
328
|
|
|
$wrappedReduceFunc = function(PromiseInterface $current, $val) use($reduceFunc, $cancellationQueue, $total, &$i) { |
329
|
12 |
|
$cancellationQueue->enqueue($val); |
330
|
|
|
return $current |
331
|
|
|
->then(function($c) use($reduceFunc, $total, &$i, $val) { |
332
|
12 |
|
return self::doResolve($val) |
333
|
12 |
|
->then(function($value) use($reduceFunc, $total, &$i, $c) { |
334
|
11 |
|
return $reduceFunc($c, $value, $i++, $total); |
335
|
12 |
|
}); |
336
|
12 |
|
}); |
337
|
15 |
|
}; |
338
|
|
|
|
339
|
15 |
|
$initialValue = self::doResolve($initialValue); |
|
|
|
|
340
|
|
|
|
341
|
15 |
|
$cancellationQueue->enqueue($initialValue); |
342
|
|
|
|
343
|
15 |
|
array_reduce($array, $wrappedReduceFunc, $initialValue) |
344
|
15 |
|
->done($resolve, $reject, $cancel); |
345
|
|
|
|
346
|
15 |
|
}, $reject, $cancel); |
347
|
15 |
|
}, $cancellationQueue); |
348
|
|
|
} |
349
|
|
|
} |
350
|
|
|
|
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.
You can also find more detailed suggestions in the “Code” section of your repository.