Skip to content

Commit

Permalink
Merge pull request #208 from clue-labs/catch-finally
Browse files Browse the repository at this point in the history
Add `catch()` and `finally()`, deprecate `otherwise()` and `always()`
  • Loading branch information
WyriHaximus authored Jan 23, 2022
2 parents 9647500 + 84322e6 commit 30284b0
Show file tree
Hide file tree
Showing 12 changed files with 590 additions and 76 deletions.
62 changes: 45 additions & 17 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,11 @@ Table of Contents
* [PromiseInterface](#promiseinterface)
* [PromiseInterface::then()](#promiseinterfacethen)
* [PromiseInterface::done()](#promiseinterfacedone)
* [PromiseInterface::otherwise()](#promiseinterfaceotherwise)
* [PromiseInterface::always()](#promiseinterfacealways)
* [PromiseInterface::catch()](#promiseinterfacecatch)
* [PromiseInterface::finally()](#promiseinterfacefinally)
* [PromiseInterface::cancel()](#promiseinterfacecancel)
* [~~PromiseInterface::otherwise()~~](#promiseinterfaceotherwise)
* [~~PromiseInterface::always()~~](#promiseinterfacealways)
* [Promise](#promise-2)
* [Functions](#functions)
* [resolve()](#resolve)
Expand Down Expand Up @@ -206,10 +208,10 @@ Since the purpose of `done()` is consumption rather than transformation,
* [PromiseInterface::then()](#promiseinterfacethen)
* [done() vs. then()](#done-vs-then)

#### PromiseInterface::otherwise()
#### PromiseInterface::catch()

```php
$promise->otherwise(callable $onRejected);
$promise->catch(callable $onRejected);
```

Registers a rejection handler for promise. It is a shortcut for:
Expand All @@ -223,19 +225,19 @@ only specific errors.

```php
$promise
->otherwise(function (\RuntimeException $reason) {
->catch(function (\RuntimeException $reason) {
// Only catch \RuntimeException instances
// All other types of errors will propagate automatically
})
->otherwise(function (\Throwable $reason) {
->catch(function (\Throwable $reason) {
// Catch other errors
});
```

#### PromiseInterface::always()
#### PromiseInterface::finally()

```php
$newPromise = $promise->always(callable $onFulfilledOrRejected);
$newPromise = $promise->finally(callable $onFulfilledOrRejected);
```

Allows you to execute "cleanup" type tasks in a promise chain.
Expand All @@ -254,15 +256,15 @@ when the promise is either fulfilled or rejected.
rejected promise, `$newPromise` will reject with the thrown exception or
rejected promise's reason.

`always()` behaves similarly to the synchronous finally statement. When combined
with `otherwise()`, `always()` allows you to write code that is similar to the familiar
`finally()` behaves similarly to the synchronous finally statement. When combined
with `catch()`, `finally()` allows you to write code that is similar to the familiar
synchronous catch/finally pair.

Consider the following synchronous code:

```php
try {
return doSomething();
return doSomething();
} catch (\Throwable $e) {
return handleError($e);
} finally {
Expand All @@ -275,8 +277,8 @@ written:

```php
return doSomething()
->otherwise('handleError')
->always('cleanup');
->catch('handleError')
->finally('cleanup');
```

#### PromiseInterface::cancel()
Expand All @@ -291,6 +293,32 @@ further interest in the results of the operation.
Once a promise is settled (either fulfilled or rejected), calling `cancel()` on
a promise has no effect.

#### ~~PromiseInterface::otherwise()~~

> Deprecated since v3.0.0, see [`catch()`](#promiseinterfacecatch) instead.
The `otherwise()` method registers a rejection handler for a promise.

This method continues to exist only for BC reasons and to ease upgrading
between versions. It is an alias for:

```php
$promise->catch($onRejected);
```

#### ~~PromiseInterface::always()~~

> Deprecated since v3.0.0, see [`finally()`](#promiseinterfacefinally) instead.
The `always()` method allows you to execute "cleanup" type tasks in a promise chain.

This method continues to exist only for BC reasons and to ease upgrading
between versions. It is an alias for:

```php
$promise->finally($onFulfilledOrRejected);
```

### Promise

Creates a promise whose state is controlled by the functions passed to
Expand Down Expand Up @@ -559,17 +587,17 @@ $deferred->promise()
->then(function ($x) {
throw new \Exception($x + 1);
})
->otherwise(function (\Exception $x) {
->catch(function (\Exception $x) {
// Propagate the rejection
throw $x;
})
->otherwise(function (\Exception $x) {
->catch(function (\Exception $x) {
// Can also propagate by returning another rejection
return React\Promise\reject(
new \Exception($x->getMessage() + 1)
);
})
->otherwise(function ($x) {
->catch(function ($x) {
echo 'Reject ' . $x->getMessage(); // 3
});

Expand All @@ -591,7 +619,7 @@ $deferred->promise()
->then(function ($x) {
throw new \Exception($x + 1);
})
->otherwise(function (\Exception $x) {
->catch(function (\Exception $x) {
// Handle the rejection, and don't propagate.
// This is like catch without a rethrow
return $x->getMessage() + 1;
Expand Down
22 changes: 20 additions & 2 deletions src/Internal/FulfilledPromise.php
Original file line number Diff line number Diff line change
Expand Up @@ -60,12 +60,12 @@ public function done(callable $onFulfilled = null, callable $onRejected = null):
});
}

public function otherwise(callable $onRejected): PromiseInterface
public function catch(callable $onRejected): PromiseInterface
{
return $this;
}

public function always(callable $onFulfilledOrRejected): PromiseInterface
public function finally(callable $onFulfilledOrRejected): PromiseInterface
{
return $this->then(function ($value) use ($onFulfilledOrRejected): PromiseInterface {
return resolve($onFulfilledOrRejected())->then(function () use ($value) {
Expand All @@ -77,4 +77,22 @@ public function always(callable $onFulfilledOrRejected): PromiseInterface
public function cancel(): void
{
}

/**
* @deprecated 3.0.0 Use `catch()` instead
* @see self::catch()
*/
public function otherwise(callable $onRejected): PromiseInterface
{
return $this->catch($onRejected);
}

/**
* @deprecated 3.0.0 Use `finally()` instead
* @see self::finally()
*/
public function always(callable $onFulfilledOrRejected): PromiseInterface
{
return $this->finally($onFulfilledOrRejected);
}
}
22 changes: 20 additions & 2 deletions src/Internal/RejectedPromise.php
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ public function done(callable $onFulfilled = null, callable $onRejected = null):
});
}

public function otherwise(callable $onRejected): PromiseInterface
public function catch(callable $onRejected): PromiseInterface
{
if (!_checkTypehint($onRejected, $this->reason)) {
return $this;
Expand All @@ -70,7 +70,7 @@ public function otherwise(callable $onRejected): PromiseInterface
return $this->then(null, $onRejected);
}

public function always(callable $onFulfilledOrRejected): PromiseInterface
public function finally(callable $onFulfilledOrRejected): PromiseInterface
{
return $this->then(null, function (\Throwable $reason) use ($onFulfilledOrRejected): PromiseInterface {
return resolve($onFulfilledOrRejected())->then(function () use ($reason): PromiseInterface {
Expand All @@ -82,4 +82,22 @@ public function always(callable $onFulfilledOrRejected): PromiseInterface
public function cancel(): void
{
}

/**
* @deprecated 3.0.0 Use `catch()` instead
* @see self::catch()
*/
public function otherwise(callable $onRejected): PromiseInterface
{
return $this->catch($onRejected);
}

/**
* @deprecated 3.0.0 Use `always()` instead
* @see self::always()
*/
public function always(callable $onFulfilledOrRejected): PromiseInterface
{
return $this->finally($onFulfilledOrRejected);
}
}
22 changes: 20 additions & 2 deletions src/Promise.php
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ public function done(callable $onFulfilled = null, callable $onRejected = null):
};
}

public function otherwise(callable $onRejected): PromiseInterface
public function catch(callable $onRejected): PromiseInterface
{
return $this->then(null, static function ($reason) use ($onRejected) {
if (!_checkTypehint($onRejected, $reason)) {
Expand All @@ -81,7 +81,7 @@ public function otherwise(callable $onRejected): PromiseInterface
});
}

public function always(callable $onFulfilledOrRejected): PromiseInterface
public function finally(callable $onFulfilledOrRejected): PromiseInterface
{
return $this->then(static function ($value) use ($onFulfilledOrRejected) {
return resolve($onFulfilledOrRejected())->then(function () use ($value) {
Expand Down Expand Up @@ -129,6 +129,24 @@ public function cancel(): void
}
}

/**
* @deprecated 3.0.0 Use `catch()` instead
* @see self::catch()
*/
public function otherwise(callable $onRejected): PromiseInterface
{
return $this->catch($onRejected);
}

/**
* @deprecated 3.0.0 Use `finally()` instead
* @see self::finally()
*/
public function always(callable $onFulfilledOrRejected): PromiseInterface
{
return $this->finally($onFulfilledOrRejected);
}

private function resolver(callable $onFulfilled = null, callable $onRejected = null): callable
{
return function ($resolve, $reject) use ($onFulfilled, $onRejected) {
Expand Down
46 changes: 40 additions & 6 deletions src/PromiseInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ public function done(callable $onFulfilled = null, callable $onRejected = null):
* @param callable $onRejected
* @return PromiseInterface
*/
public function otherwise(callable $onRejected): PromiseInterface;
public function catch(callable $onRejected): PromiseInterface;

/**
* Allows you to execute "cleanup" type tasks in a promise chain.
Expand All @@ -82,8 +82,8 @@ public function otherwise(callable $onRejected): PromiseInterface;
* rejected promise, `$newPromise` will reject with the thrown exception or
* rejected promise's reason.
*
* `always()` behaves similarly to the synchronous finally statement. When combined
* with `otherwise()`, `always()` allows you to write code that is similar to the familiar
* `finally()` behaves similarly to the synchronous finally statement. When combined
* with `catch()`, `finally()` allows you to write code that is similar to the familiar
* synchronous catch/finally pair.
*
* Consider the following synchronous code:
Expand All @@ -103,14 +103,14 @@ public function otherwise(callable $onRejected): PromiseInterface;
*
* ```php
* return doSomething()
* ->otherwise('handleError')
* ->always('cleanup');
* ->catch('handleError')
* ->finally('cleanup');
* ```
*
* @param callable $onFulfilledOrRejected
* @return PromiseInterface
*/
public function always(callable $onFulfilledOrRejected): PromiseInterface;
public function finally(callable $onFulfilledOrRejected): PromiseInterface;

/**
* The `cancel()` method notifies the creator of the promise that there is no
Expand All @@ -122,4 +122,38 @@ public function always(callable $onFulfilledOrRejected): PromiseInterface;
* @return void
*/
public function cancel(): void;

/**
* [Deprecated] Registers a rejection handler for a promise.
*
* This method continues to exist only for BC reasons and to ease upgrading
* between versions. It is an alias for:
*
* ```php
* $promise->catch($onRejected);
* ```
*
* @param callable $onRejected
* @return PromiseInterface
* @deprecated 3.0.0 Use catch() instead
* @see self::catch()
*/
public function otherwise(callable $onRejected): PromiseInterface;

/**
* [Deprecated] Allows you to execute "cleanup" type tasks in a promise chain.
*
* This method continues to exist only for BC reasons and to ease upgrading
* between versions. It is an alias for:
*
* ```php
* $promise->finally($onFulfilledOrRejected);
* ```
*
* @param callable $onFulfilledOrRejected
* @return PromiseInterface
* @deprecated 3.0.0 Use finally() instead
* @see self::finally()
*/
public function always(callable $onFulfilledOrRejected): PromiseInterface;
}
30 changes: 29 additions & 1 deletion tests/PromiseTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,31 @@ public function shouldNotLeaveGarbageCyclesWhenRemovingLastReferenceToPendingPro
}

/** @test */
public function shouldNotLeaveGarbageCyclesWhenRemovingLastReferenceToPendingPromiseWithCatchFollowers()
{
gc_collect_cycles();
$promise = new Promise(function () { });
$promise->catch(function () { });
unset($promise);

$this->assertSame(0, gc_collect_cycles());
}

/** @test */
public function shouldNotLeaveGarbageCyclesWhenRemovingLastReferenceToPendingPromiseWithFinallyFollowers()
{
gc_collect_cycles();
$promise = new Promise(function () { });
$promise->finally(function () { });
unset($promise);

$this->assertSame(0, gc_collect_cycles());
}

/**
* @test
* @deprecated
*/
public function shouldNotLeaveGarbageCyclesWhenRemovingLastReferenceToPendingPromiseWithOtherwiseFollowers()
{
gc_collect_cycles();
Expand All @@ -234,7 +259,10 @@ public function shouldNotLeaveGarbageCyclesWhenRemovingLastReferenceToPendingPro
$this->assertSame(0, gc_collect_cycles());
}

/** @test */
/**
* @test
* @deprecated
*/
public function shouldNotLeaveGarbageCyclesWhenRemovingLastReferenceToPendingPromiseWithAlwaysFollowers()
{
gc_collect_cycles();
Expand Down
Loading

0 comments on commit 30284b0

Please sign in to comment.