Skip to content
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: reactphp/async
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: v4.1.0
Choose a base ref
...
head repository: reactphp/async
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: v4.2.0
Choose a head ref
  • 7 commits
  • 19 files changed
  • 3 contributors

Commits on Jul 7, 2023

  1. Verified

    This commit was signed with the committer’s verified signature.
    jmikola Jeremy Mikola
    Copy the full SHA
    9b58514 View commit details
  2. Merge pull request #79 from clue-labs/unhandled-rejections-v4

    [4.x] Update test suite to avoid unhandled promise rejections
    WyriHaximus authored Jul 7, 2023
    Copy the full SHA
    307684c View commit details

Commits on Oct 27, 2023

  1. Add template annotations

    These annotations will aid static analyses like PHPStan and Psalm to
    enhance type-safety for this project and projects depending on it
    
    These changes make the following example understandable by PHPStan:
    
    ```php
    final readonly class User
    {
        public function __construct(
            public string $name,
        )
    }
    
    /**
     * \React\Promise\PromiseInterface<User>
     */
    function getCurrentUserFromDatabase(): \React\Promise\PromiseInterface
    {
        // The following line would do the database query and fetch the
    result from it
        // but keeping it simple for the sake of the example.
        return \React\Promise\resolve(new User('WyriHaximus'));
    }
    
    // For the sake of this example we're going to assume the following code
    runs
    // in \React\Async\async call
    echo await(getCurrentUserFromDatabase())->name; // This echos:
    WyriHaximus
    ```
    WyriHaximus authored and clue committed Oct 27, 2023
    Copy the full SHA
    643316a View commit details
  2. Merge pull request #40 from WyriHaximus-labs/4.x-add-template-annotat…

    …ions
    
    [4.x] Add template annotations
    WyriHaximus authored Oct 27, 2023
    Copy the full SHA
    8cc37cc View commit details

Commits on Oct 31, 2023

  1. Copy the full SHA
    9eb6332 View commit details

Commits on Nov 3, 2023

  1. Merge pull request #81 from clue-labs/php8.3-v4

    [4.x] Run tests on PHP 8.3 and update test suite
    WyriHaximus authored Nov 3, 2023
    Copy the full SHA
    3fad975 View commit details

Commits on Nov 22, 2023

  1. Prepare v4.2.0 release

    SimonFrings committed Nov 22, 2023
    Copy the full SHA
    7c3738e View commit details
6 changes: 4 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -11,10 +11,11 @@ jobs:
strategy:
matrix:
php:
- 8.3
- 8.2
- 8.1
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php }}
@@ -29,10 +30,11 @@ jobs:
strategy:
matrix:
php:
- 8.3
- 8.2
- 8.1
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php }}
21 changes: 21 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,26 @@
# Changelog

## 4.2.0 (2023-11-22)

* Feature: Add Promise v3 template types for all public functions.
(#40 by @WyriHaximus)

All our public APIs now use Promise v3 template types to guide IDEs and static
analysis tools (like PHPStan), helping with proper type usage and improving
code quality:

```php
assertType('bool', await(resolve(true)));
assertType('PromiseInterface<bool>', async(fn(): bool => true)());
assertType('PromiseInterface<bool>', coroutine(fn(): bool => true));
```

* Feature: Full PHP 8.3 compatibility.
(#81 by @clue)

* Update test suite to avoid unhandled promise rejections.
(#79 by @clue)

## 4.1.0 (2023-06-22)

* Feature: Add new `delay()` function to delay program execution.
14 changes: 7 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
@@ -58,7 +58,7 @@ Async\await(…);

### async()

The `async(callable $function): callable` function can be used to
The `async(callable():(PromiseInterface<T>|T) $function): (callable():PromiseInterface<T>)` function can be used to
return an async function for a function that uses [`await()`](#await) internally.

This function is specifically designed to complement the [`await()` function](#await).
@@ -226,7 +226,7 @@ await($promise);

### await()

The `await(PromiseInterface $promise): mixed` function can be used to
The `await(PromiseInterface<T> $promise): T` function can be used to
block waiting for the given `$promise` to be fulfilled.

```php
@@ -278,7 +278,7 @@ try {

### coroutine()

The `coroutine(callable $function, mixed ...$args): PromiseInterface<mixed>` function can be used to
The `coroutine(callable(mixed ...$args):(\Generator|PromiseInterface<T>|T) $function, mixed ...$args): PromiseInterface<T>` function can be used to
execute a Generator-based coroutine to "await" promises.

```php
@@ -498,7 +498,7 @@ Loop::addTimer(2.0, function () use ($promise): void {

### parallel()

The `parallel(iterable<callable():PromiseInterface<mixed>> $tasks): PromiseInterface<array<mixed>>` function can be used
The `parallel(iterable<callable():PromiseInterface<T>> $tasks): PromiseInterface<array<T>>` function can be used
like this:

```php
@@ -540,7 +540,7 @@ React\Async\parallel([

### series()

The `series(iterable<callable():PromiseInterface<mixed>> $tasks): PromiseInterface<array<mixed>>` function can be used
The `series(iterable<callable():PromiseInterface<T>> $tasks): PromiseInterface<array<T>>` function can be used
like this:

```php
@@ -582,7 +582,7 @@ React\Async\series([

### waterfall()

The `waterfall(iterable<callable(mixed=):PromiseInterface<mixed>> $tasks): PromiseInterface<mixed>` function can be used
The `waterfall(iterable<callable(mixed=):PromiseInterface<T>> $tasks): PromiseInterface<T>` function can be used
like this:

```php
@@ -623,7 +623,7 @@ This project follows [SemVer](https://semver.org/).
This will install the latest supported version from this branch:

```bash
composer require react/async:^4.1
composer require react/async:^4.2
```

See also the [CHANGELOG](CHANGELOG.md) for details about version upgrades.
8 changes: 5 additions & 3 deletions composer.json
Original file line number Diff line number Diff line change
@@ -31,8 +31,8 @@
"react/promise": "^3.0 || ^2.8 || ^1.2.1"
},
"require-dev": {
"phpstan/phpstan": "1.10.18",
"phpunit/phpunit": "^9.5"
"phpstan/phpstan": "1.10.39",
"phpunit/phpunit": "^9.6"
},
"autoload": {
"psr-4": {
@@ -43,6 +43,8 @@
]
},
"autoload-dev": {
"psr-4": { "React\\Tests\\Async\\": "tests/" }
"psr-4": {
"React\\Tests\\Async\\": "tests/"
}
}
}
4 changes: 2 additions & 2 deletions phpunit.xml.dist
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>

<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/9.5/phpunit.xsd"
xsi:noNamespaceSchemaLocation="vendor/phpunit/phpunit/phpunit.xsd"
bootstrap="vendor/autoload.php"
cacheResult="false"
colors="true"
@@ -19,7 +19,7 @@
<php>
<ini name="error_reporting" value="-1" />
<!-- Evaluate assertions, requires running with "php -d zend.assertions=1 vendor/bin/phpunit" -->
<!-- <ini name="zend.assertions=1" value="1" /> -->
<!-- <ini name="zend.assertions" value="1" /> -->
<ini name="assert.active" value="1" />
<ini name="assert.exception" value="1" />
<ini name="assert.bail" value="0" />
19 changes: 15 additions & 4 deletions src/FiberMap.php
Original file line number Diff line number Diff line change
@@ -6,13 +6,15 @@

/**
* @internal
*
* @template T
*/
final class FiberMap
{
/** @var array<int,bool> */
private static array $status = [];

/** @var array<int,PromiseInterface> */
/** @var array<int,PromiseInterface<T>> */
private static array $map = [];

/** @param \Fiber<mixed,mixed,mixed,mixed> $fiber */
@@ -27,19 +29,28 @@ public static function cancel(\Fiber $fiber): void
self::$status[\spl_object_id($fiber)] = true;
}

/** @param \Fiber<mixed,mixed,mixed,mixed> $fiber */
/**
* @param \Fiber<mixed,mixed,mixed,mixed> $fiber
* @param PromiseInterface<T> $promise
*/
public static function setPromise(\Fiber $fiber, PromiseInterface $promise): void
{
self::$map[\spl_object_id($fiber)] = $promise;
}

/** @param \Fiber<mixed,mixed,mixed,mixed> $fiber */
/**
* @param \Fiber<mixed,mixed,mixed,mixed> $fiber
* @param PromiseInterface<T> $promise
*/
public static function unsetPromise(\Fiber $fiber, PromiseInterface $promise): void
{
unset(self::$map[\spl_object_id($fiber)]);
}

/** @param \Fiber<mixed,mixed,mixed,mixed> $fiber */
/**
* @param \Fiber<mixed,mixed,mixed,mixed> $fiber
* @return ?PromiseInterface<T>
*/
public static function getPromise(\Fiber $fiber): ?PromiseInterface
{
return self::$map[\spl_object_id($fiber)] ?? null;
56 changes: 38 additions & 18 deletions src/functions.php
Original file line number Diff line number Diff line change
@@ -176,8 +176,14 @@
* await($promise);
* ```
*
* @param callable $function
* @return callable(mixed ...): PromiseInterface<mixed>
* @template T
* @template A1 (any number of function arguments, see https://github.com/phpstan/phpstan/issues/8214)
* @template A2
* @template A3
* @template A4
* @template A5
* @param callable(A1,A2,A3,A4,A5): (PromiseInterface<T>|T) $function
* @return callable(A1=,A2=,A3=,A4=,A5=): PromiseInterface<T>
* @since 4.0.0
* @see coroutine()
*/
@@ -268,8 +274,9 @@ function async(callable $function): callable
* }
* ```
*
* @param PromiseInterface $promise
* @return mixed returns whatever the promise resolves to
* @template T
* @param PromiseInterface<T> $promise
* @return T returns whatever the promise resolves to
* @throws \Exception when the promise is rejected with an `Exception`
* @throws \Throwable when the promise is rejected with a `Throwable`
* @throws \UnexpectedValueException when the promise is rejected with an unexpected value (Promise API v1 or v2 only)
@@ -279,6 +286,8 @@ function await(PromiseInterface $promise): mixed
$fiber = null;
$resolved = false;
$rejected = false;

/** @var T $resolvedValue */
$resolvedValue = null;
$rejectedThrowable = null;
$lowLevelFiber = \Fiber::getCurrent();
@@ -292,6 +301,7 @@ function (mixed $value) use (&$resolved, &$resolvedValue, &$fiber, $lowLevelFibe
/** @var ?\Fiber<mixed,mixed,mixed,mixed> $fiber */
if ($fiber === null) {
$resolved = true;
/** @var T $resolvedValue */
$resolvedValue = $value;
return;
}
@@ -305,7 +315,7 @@ function (mixed $throwable) use (&$rejected, &$rejectedThrowable, &$fiber, $lowL

if (!$throwable instanceof \Throwable) {
$throwable = new \UnexpectedValueException(
'Promise rejected with unexpected value of type ' . (is_object($throwable) ? get_class($throwable) : gettype($throwable))
'Promise rejected with unexpected value of type ' . (is_object($throwable) ? get_class($throwable) : gettype($throwable)) /** @phpstan-ignore-line */
);

// avoid garbage references by replacing all closures in call stack.
@@ -592,9 +602,16 @@ function delay(float $seconds): void
* });
* ```
*
* @param callable(mixed ...$args):(\Generator<mixed,PromiseInterface,mixed,mixed>|mixed) $function
* @template T
* @template TYield
* @template A1 (any number of function arguments, see https://github.com/phpstan/phpstan/issues/8214)
* @template A2
* @template A3
* @template A4
* @template A5
* @param callable(A1, A2, A3, A4, A5):(\Generator<mixed, PromiseInterface<TYield>, TYield, PromiseInterface<T>|T>|PromiseInterface<T>|T) $function
* @param mixed ...$args Optional list of additional arguments that will be passed to the given `$function` as is
* @return PromiseInterface<mixed>
* @return PromiseInterface<T>
* @since 3.0.0
*/
function coroutine(callable $function, mixed ...$args): PromiseInterface
@@ -611,7 +628,7 @@ function coroutine(callable $function, mixed ...$args): PromiseInterface

$promise = null;
$deferred = new Deferred(function () use (&$promise) {
/** @var ?PromiseInterface $promise */
/** @var ?PromiseInterface<T> $promise */
if ($promise instanceof PromiseInterface && \method_exists($promise, 'cancel')) {
$promise->cancel();
}
@@ -632,7 +649,6 @@ function coroutine(callable $function, mixed ...$args): PromiseInterface
return;
}

/** @var mixed $promise */
$promise = $generator->current();
if (!$promise instanceof PromiseInterface) {
$next = null;
@@ -642,6 +658,7 @@ function coroutine(callable $function, mixed ...$args): PromiseInterface
return;
}

/** @var PromiseInterface<TYield> $promise */
assert($next instanceof \Closure);
$promise->then(function ($value) use ($generator, $next) {
$generator->send($value);
@@ -660,12 +677,13 @@ function coroutine(callable $function, mixed ...$args): PromiseInterface
}

/**
* @param iterable<callable():PromiseInterface<mixed>> $tasks
* @return PromiseInterface<array<mixed>>
* @template T
* @param iterable<callable():(PromiseInterface<T>|T)> $tasks
* @return PromiseInterface<array<T>>
*/
function parallel(iterable $tasks): PromiseInterface
{
/** @var array<int,PromiseInterface> $pending */
/** @var array<int,PromiseInterface<T>> $pending */
$pending = [];
$deferred = new Deferred(function () use (&$pending) {
foreach ($pending as $promise) {
@@ -720,14 +738,15 @@ function parallel(iterable $tasks): PromiseInterface
}

/**
* @param iterable<callable():PromiseInterface<mixed>> $tasks
* @return PromiseInterface<array<mixed>>
* @template T
* @param iterable<callable():(PromiseInterface<T>|T)> $tasks
* @return PromiseInterface<array<T>>
*/
function series(iterable $tasks): PromiseInterface
{
$pending = null;
$deferred = new Deferred(function () use (&$pending) {
/** @var ?PromiseInterface $pending */
/** @var ?PromiseInterface<T> $pending */
if ($pending instanceof PromiseInterface && \method_exists($pending, 'cancel')) {
$pending->cancel();
}
@@ -774,14 +793,15 @@ function series(iterable $tasks): PromiseInterface
}

/**
* @param iterable<(callable():PromiseInterface<mixed>)|(callable(mixed):PromiseInterface<mixed>)> $tasks
* @return PromiseInterface<mixed>
* @template T
* @param iterable<(callable():(PromiseInterface<T>|T))|(callable(mixed):(PromiseInterface<T>|T))> $tasks
* @return PromiseInterface<($tasks is non-empty-array|\Traversable ? T : null)>
*/
function waterfall(iterable $tasks): PromiseInterface
{
$pending = null;
$deferred = new Deferred(function () use (&$pending) {
/** @var ?PromiseInterface $pending */
/** @var ?PromiseInterface<T> $pending */
if ($pending instanceof PromiseInterface && \method_exists($pending, 'cancel')) {
$pending->cancel();
}
3 changes: 2 additions & 1 deletion src/functions_include.php
Original file line number Diff line number Diff line change
@@ -3,6 +3,7 @@
namespace React\Async;

// @codeCoverageIgnoreStart
if (!function_exists(__NAMESPACE__ . '\\parallel')) {
if (!\function_exists(__NAMESPACE__ . '\\parallel')) {
require __DIR__ . '/functions.php';
}
// @codeCoverageIgnoreEnd
2 changes: 1 addition & 1 deletion tests/AwaitTest.php
Original file line number Diff line number Diff line change
@@ -413,7 +413,7 @@ public function testRejectedPromisesShouldBeDetached(callable $await): void
})());
}

/** @return iterable<string,list<callable(PromiseInterface): mixed>> */
/** @return iterable<string,list<callable(PromiseInterface<mixed>): mixed>> */
public function provideAwaiters(): iterable
{
yield 'await' => [static fn (React\Promise\PromiseInterface $promise): mixed => React\Async\await($promise)];
Loading