Skip to content

Commit e63bcec

Browse files
authored
Merge pull request #225 from clue-labs/iterable
Support `iterable` type for `all()` + `race()` + `any()`
2 parents 93d4b0f + 57fa798 commit e63bcec

File tree

5 files changed

+261
-65
lines changed

5 files changed

+261
-65
lines changed

README.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -376,7 +376,7 @@ reject a promise, any language error or user land exception can be used to rejec
376376
#### all()
377377

378378
```php
379-
$promise = React\Promise\all(array $promisesOrValues);
379+
$promise = React\Promise\all(iterable $promisesOrValues);
380380
```
381381

382382
Returns a promise that will resolve only once all the items in
@@ -387,7 +387,7 @@ will be an array containing the resolution values of each of the items in
387387
#### race()
388388

389389
```php
390-
$promise = React\Promise\race(array $promisesOrValues);
390+
$promise = React\Promise\race(iterable $promisesOrValues);
391391
```
392392

393393
Initiates a competitive race that allows one winner. Returns a promise which is
@@ -399,7 +399,7 @@ contains 0 items.
399399
#### any()
400400

401401
```php
402-
$promise = React\Promise\any(array $promisesOrValues);
402+
$promise = React\Promise\any(iterable $promisesOrValues);
403403
```
404404

405405
Returns a promise that will resolve when any one of the items in

src/functions.php

Lines changed: 74 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -68,36 +68,45 @@ function reject(\Throwable $reason): PromiseInterface
6868
* will be an array containing the resolution values of each of the items in
6969
* `$promisesOrValues`.
7070
*
71-
* @param array $promisesOrValues
71+
* @param iterable $promisesOrValues
7272
* @return PromiseInterface
7373
*/
74-
function all(array $promisesOrValues): PromiseInterface
74+
function all(iterable $promisesOrValues): PromiseInterface
7575
{
76-
if (!$promisesOrValues) {
77-
return resolve([]);
78-
}
79-
8076
$cancellationQueue = new Internal\CancellationQueue();
8177

8278
return new Promise(function ($resolve, $reject) use ($promisesOrValues, $cancellationQueue): void {
83-
$toResolve = \count($promisesOrValues);
79+
$toResolve = 0;
80+
$continue = true;
8481
$values = [];
8582

8683
foreach ($promisesOrValues as $i => $promiseOrValue) {
8784
$cancellationQueue->enqueue($promiseOrValue);
8885
$values[$i] = null;
86+
++$toResolve;
87+
88+
resolve($promiseOrValue)->then(
89+
function ($value) use ($i, &$values, &$toResolve, &$continue, $resolve): void {
90+
$values[$i] = $value;
91+
92+
if (0 === --$toResolve && !$continue) {
93+
$resolve($values);
94+
}
95+
},
96+
function (\Throwable $reason) use (&$continue, $reject): void {
97+
$continue = false;
98+
$reject($reason);
99+
}
100+
);
101+
102+
if (!$continue) {
103+
break;
104+
}
105+
}
89106

90-
resolve($promiseOrValue)
91-
->then(
92-
function ($mapped) use ($i, &$values, &$toResolve, $resolve): void {
93-
$values[$i] = $mapped;
94-
95-
if (0 === --$toResolve) {
96-
$resolve($values);
97-
}
98-
},
99-
$reject
100-
);
107+
$continue = false;
108+
if ($toResolve === 0) {
109+
$resolve($values);
101110
}
102111
}, $cancellationQueue);
103112
}
@@ -109,23 +118,26 @@ function ($mapped) use ($i, &$values, &$toResolve, $resolve): void {
109118
* The returned promise will become **infinitely pending** if `$promisesOrValues`
110119
* contains 0 items.
111120
*
112-
* @param array $promisesOrValues
121+
* @param iterable $promisesOrValues
113122
* @return PromiseInterface
114123
*/
115-
function race(array $promisesOrValues): PromiseInterface
124+
function race(iterable $promisesOrValues): PromiseInterface
116125
{
117-
if (!$promisesOrValues) {
118-
return new Promise(function (): void {});
119-
}
120-
121126
$cancellationQueue = new Internal\CancellationQueue();
122127

123128
return new Promise(function ($resolve, $reject) use ($promisesOrValues, $cancellationQueue): void {
129+
$continue = true;
130+
124131
foreach ($promisesOrValues as $promiseOrValue) {
125132
$cancellationQueue->enqueue($promiseOrValue);
126133

127-
resolve($promiseOrValue)
128-
->then($resolve, $reject);
134+
resolve($promiseOrValue)->then($resolve, $reject)->finally(function () use (&$continue): void {
135+
$continue = false;
136+
});
137+
138+
if (!$continue) {
139+
break;
140+
}
129141
}
130142
}, $cancellationQueue);
131143
}
@@ -141,53 +153,54 @@ function race(array $promisesOrValues): PromiseInterface
141153
* The returned promise will also reject with a `React\Promise\Exception\LengthException`
142154
* if `$promisesOrValues` contains 0 items.
143155
*
144-
* @param array $promisesOrValues
156+
* @param iterable $promisesOrValues
145157
* @return PromiseInterface
146158
*/
147-
function any(array $promisesOrValues): PromiseInterface
159+
function any(iterable $promisesOrValues): PromiseInterface
148160
{
149-
$len = \count($promisesOrValues);
150-
151-
if (!$promisesOrValues) {
152-
return reject(
153-
new Exception\LengthException(
154-
\sprintf(
155-
'Input array must contain at least 1 item but contains only %s item%s.',
156-
$len,
157-
1 === $len ? '' : 's'
158-
)
159-
)
160-
);
161-
}
162-
163161
$cancellationQueue = new Internal\CancellationQueue();
164162

165-
return new Promise(function ($resolve, $reject) use ($len, $promisesOrValues, $cancellationQueue): void {
166-
$toReject = $len;
167-
$reasons = [];
163+
return new Promise(function ($resolve, $reject) use ($promisesOrValues, $cancellationQueue): void {
164+
$toReject = 0;
165+
$continue = true;
166+
$reasons = [];
168167

169168
foreach ($promisesOrValues as $i => $promiseOrValue) {
170-
$fulfiller = function ($val) use ($resolve): void {
171-
$resolve($val);
172-
};
173-
174-
$rejecter = function (\Throwable $reason) use ($i, &$reasons, &$toReject, $reject): void {
175-
$reasons[$i] = $reason;
176-
177-
if (0 === --$toReject) {
178-
$reject(
179-
new CompositeException(
169+
$cancellationQueue->enqueue($promiseOrValue);
170+
++$toReject;
171+
172+
resolve($promiseOrValue)->then(
173+
function ($value) use ($resolve, &$continue): void {
174+
$continue = false;
175+
$resolve($value);
176+
},
177+
function (\Throwable $reason) use ($i, &$reasons, &$toReject, $reject, &$continue): void {
178+
$reasons[$i] = $reason;
179+
180+
if (0 === --$toReject && !$continue) {
181+
$reject(new CompositeException(
180182
$reasons,
181183
'All promises rejected.'
182-
)
183-
);
184+
));
185+
}
184186
}
185-
};
187+
);
186188

187-
$cancellationQueue->enqueue($promiseOrValue);
189+
if (!$continue) {
190+
break;
191+
}
192+
}
188193

189-
resolve($promiseOrValue)
190-
->then($fulfiller, $rejecter);
194+
$continue = false;
195+
if ($toReject === 0 && !$reasons) {
196+
$reject(new Exception\LengthException(
197+
'Must contain at least 1 item but contains only 0 items.'
198+
));
199+
} elseif ($toReject === 0) {
200+
$reject(new CompositeException(
201+
$reasons,
202+
'All promises rejected.'
203+
));
191204
}
192205
}, $cancellationQueue);
193206
}

tests/FunctionAllTest.php

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,42 @@ public function shouldResolveSparseArrayInput()
5858
->then($mock);
5959
}
6060

61+
/** @test */
62+
public function shouldResolveValuesGenerator()
63+
{
64+
$mock = $this->createCallableMock();
65+
$mock
66+
->expects(self::once())
67+
->method('__invoke')
68+
->with(self::identicalTo([1, 2, 3]));
69+
70+
$gen = (function () {
71+
for ($i = 1; $i <= 3; ++$i) {
72+
yield $i;
73+
}
74+
})();
75+
76+
all($gen)->then($mock);
77+
}
78+
79+
/** @test */
80+
public function shouldResolveValuesGeneratorEmpty()
81+
{
82+
$mock = $this->createCallableMock();
83+
$mock
84+
->expects(self::once())
85+
->method('__invoke')
86+
->with(self::identicalTo([]));
87+
88+
$gen = (function () {
89+
if (false) {
90+
yield;
91+
}
92+
})();
93+
94+
all($gen)->then($mock);
95+
}
96+
6197
/** @test */
6298
public function shouldRejectIfAnyInputPromiseRejects()
6399
{
@@ -74,6 +110,24 @@ public function shouldRejectIfAnyInputPromiseRejects()
74110
->then($this->expectCallableNever(), $mock);
75111
}
76112

113+
/** @test */
114+
public function shouldRejectInfiteGeneratorOrRejectedPromises()
115+
{
116+
$mock = $this->createCallableMock();
117+
$mock
118+
->expects(self::once())
119+
->method('__invoke')
120+
->with(new \RuntimeException('Iteration 1'));
121+
122+
$gen = (function () {
123+
for ($i = 1; ; ++$i) {
124+
yield reject(new \RuntimeException('Iteration ' . $i));
125+
}
126+
})();
127+
128+
all($gen)->then(null, $mock);
129+
}
130+
77131
/** @test */
78132
public function shouldPreserveTheOrderOfArrayWhenResolvingAsyncPromises()
79133
{

0 commit comments

Comments
 (0)