Skip to content

Commit

Permalink
Add Promise::all (#6152)
Browse files Browse the repository at this point in the history
  • Loading branch information
ShockedPlot7560 committed Feb 6, 2024
1 parent 20837c9 commit 6bb84bc
Show file tree
Hide file tree
Showing 3 changed files with 171 additions and 0 deletions.
50 changes: 50 additions & 0 deletions src/promise/Promise.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@

namespace pocketmine\promise;

use function count;
use function spl_object_id;

/**
Expand Down Expand Up @@ -57,4 +58,53 @@ public function isResolved() : bool{
//rejected or just hasn't been resolved yet
return $this->shared->state === true;
}

/**
* Returns a promise that will resolve only once all the Promises in
* `$promises` have resolved. The resolution value of the returned promise
* will be an array containing the resolution values of each Promises in
* `$promises` indexed by the respective Promises' array keys.
*
* @param Promise[] $promises
*
* @phpstan-template TPromiseValue
* @phpstan-template TKey of array-key
* @phpstan-param non-empty-array<TKey, Promise<TPromiseValue>> $promises
*
* @phpstan-return Promise<array<TKey, TPromiseValue>>
*/
public static function all(array $promises) : Promise{
if(count($promises) === 0){
throw new \InvalidArgumentException("At least one promise must be provided");
}
/** @phpstan-var PromiseResolver<array<TKey, TPromiseValue>> $resolver */
$resolver = new PromiseResolver();
$values = [];
$toResolve = count($promises);
$continue = true;

foreach($promises as $key => $promise){
$promise->onCompletion(
function(mixed $value) use ($resolver, $key, $toResolve, &$values) : void{
$values[$key] = $value;

if(count($values) === $toResolve){
$resolver->resolve($values);
}
},
function() use ($resolver, &$continue) : void{
if($continue){
$continue = false;
$resolver->reject();
}
}
);

if(!$continue){
break;
}
}

return $resolver->getPromise();
}
}
15 changes: 15 additions & 0 deletions tests/phpstan/configs/phpstan-bugs.neon
Original file line number Diff line number Diff line change
Expand Up @@ -35,3 +35,18 @@ parameters:
count: 1
path: ../../../src/world/generator/normal/Normal.php

-
message: "#^Call to static method PHPUnit\\\\Framework\\\\Assert\\:\\:assertFalse\\(\\) with false will always evaluate to true\\.$#"
count: 1
path: ../../phpunit/promise/PromiseTest.php

-
message: "#^Call to static method PHPUnit\\\\Framework\\\\Assert\\:\\:assertTrue\\(\\) with false and 'All promise should…' will always evaluate to false\\.$#"
count: 1
path: ../../phpunit/promise/PromiseTest.php

-
message: "#^Call to static method PHPUnit\\\\Framework\\\\Assert\\:\\:assertTrue\\(\\) with false will always evaluate to false\\.$#"
count: 2
path: ../../phpunit/promise/PromiseTest.php

106 changes: 106 additions & 0 deletions tests/phpunit/promise/PromiseTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -39,4 +39,110 @@ function() : void{
}
);
}

public function testAllPreResolved() : void{
$resolver = new PromiseResolver();
$resolver->resolve(1);

$allPromise = Promise::all([$resolver->getPromise()]);
$done = false;
$allPromise->onCompletion(
function($value) use (&$done) : void{
$done = true;
self::assertEquals([1], $value);
},
function() use (&$done) : void{
$done = true;
self::fail("Promise was rejected");
}
);
self::assertTrue($done);
}

public function testAllPostResolved() : void{
$resolver = new PromiseResolver();

$allPromise = Promise::all([$resolver->getPromise()]);
$done = false;
$allPromise->onCompletion(
function($value) use (&$done) : void{
$done = true;
self::assertEquals([1], $value);
},
function() use (&$done) : void{
$done = true;
self::fail("Promise was rejected");
}
);
self::assertFalse($done);
$resolver->resolve(1);
self::assertTrue($done);
}

public function testAllResolve() : void{
$resolver1 = new PromiseResolver();
$resolver2 = new PromiseResolver();

$allPromise = Promise::all([$resolver1->getPromise(), $resolver2->getPromise()]);
$done = false;
$allPromise->onCompletion(
function($value) use (&$done) : void{
$done = true;
self::assertEquals([1, 2], $value);
},
function() use (&$done) : void{
$done = true;
self::fail("Promise was rejected");
}
);
self::assertFalse($done);
$resolver1->resolve(1);
self::assertFalse($done);
$resolver2->resolve(2);
self::assertTrue($done);
}

public function testAllPartialReject() : void{
$resolver1 = new PromiseResolver();
$resolver2 = new PromiseResolver();

$allPromise = Promise::all([$resolver1->getPromise(), $resolver2->getPromise()]);
$done = false;
$allPromise->onCompletion(
function($value) use (&$done) : void{
$done = true;
self::fail("Promise was unexpectedly resolved");
},
function() use (&$done) : void{
$done = true;
}
);
self::assertFalse($done);
$resolver2->reject();
self::assertTrue($done, "All promise should be rejected immediately after the first constituent rejection");
$resolver1->resolve(1);
}

/**
* Promise::all() should return a rejected promise if any of the input promises were rejected at the call time
*/
public function testAllPartialPreReject() : void{
$resolver1 = new PromiseResolver();
$resolver2 = new PromiseResolver();
$resolver2->reject();

$allPromise = Promise::all([$resolver1->getPromise(), $resolver2->getPromise()]);
$done = false;
$allPromise->onCompletion(
function($value) use (&$done) : void{
$done = true;
self::fail("Promise was unexpectedly resolved");
},
function() use (&$done) : void{
$done = true;
}
);
self::assertTrue($done, "All promise should be rejected immediately after the first constituent rejection");
$resolver1->resolve(1);
}
}

0 comments on commit 6bb84bc

Please sign in to comment.