Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Promise::all #6152

Merged
merged 17 commits into from
Feb 6, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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){
ShockedPlot7560 marked this conversation as resolved.
Show resolved Hide resolved
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);
}
}
Loading