Skip to content
Closed
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
3 changes: 2 additions & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@
{"name": "Jan Sorgalla", "email": "jsorgalla@gmail.com"}
],
"require": {
"php": ">=5.4.0"
"php": ">=5.4.0",
"async-interop/promise": "^0.2.0"
},
"autoload": {
"psr-4": {
Expand Down
11 changes: 9 additions & 2 deletions src/FulfilledPromise.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,15 @@

namespace React\Promise;

class FulfilledPromise implements ExtendedPromiseInterface, CancellablePromiseInterface
use Interop\Async\Promise as AsyncInteropPromise;

class FulfilledPromise implements ExtendedPromiseInterface, CancellablePromiseInterface, AsyncInteropPromise
{
private $value;

public function __construct($value = null)
{
if ($value instanceof PromiseInterface) {
if ($value instanceof PromiseInterface || $value instanceof AsyncInteropPromise) {
throw new \InvalidArgumentException('You cannot create React\Promise\FulfilledPromise with a promise. Use React\Promise\resolve($promiseOrValue) instead.');
}

Expand Down Expand Up @@ -65,4 +67,9 @@ public function progress(callable $onProgress)
public function cancel()
{
}

public function when(callable $onResolved)
{
$onResolved(null, $this->value);
}
}
9 changes: 8 additions & 1 deletion src/LazyPromise.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@

namespace React\Promise;

class LazyPromise implements ExtendedPromiseInterface, CancellablePromiseInterface
use Interop\Async\Promise as AsyncInteropPromise;

class LazyPromise implements ExtendedPromiseInterface, CancellablePromiseInterface, AsyncInteropPromise
{
private $factory;
private $promise;
Expand Down Expand Up @@ -42,6 +44,11 @@ public function cancel()
return $this->promise()->cancel();
}

public function when(callable $onResolved)
{
return $this->promise()->when($onResolved);
}

/**
* @internal
* @see Promise::settle()
Expand Down
16 changes: 15 additions & 1 deletion src/Promise.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@

namespace React\Promise;

class Promise implements ExtendedPromiseInterface, CancellablePromiseInterface
use Interop\Async\Promise as AsyncInteropPromise;

class Promise implements ExtendedPromiseInterface, CancellablePromiseInterface, AsyncInteropPromise
{
private $canceller;
private $result;
Expand Down Expand Up @@ -97,6 +99,18 @@ public function cancel()
$this->call($canceller);
}

public function when(callable $onResolved)
{
$this->done(function ($value) use ($onResolved) {
$onResolved(null, $value);
}, function ($reason) use ($onResolved) {
$onResolved(
UnhandledRejectionException::resolve($reason),
null
);
});
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Both $onResolved calls should be wrapped with try / catch blocks and be forwarded to the error handler, currently the one of Loop, once the PR is merged, to the promise error handler: async-interop/promise#19

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, but since we're requiring ^0.2.0 and do not require async-interop/event-loop there is no other way at the moment.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I just fixed my patch, so we can merge the PR soon and have a new release hopefully.

}

private function resolver(callable $onFulfilled = null, callable $onRejected = null, callable $onProgress = null)
{
return function ($resolve, $reject, $notify) use ($onFulfilled, $onRejected, $onProgress) {
Expand Down
14 changes: 12 additions & 2 deletions src/RejectedPromise.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,15 @@

namespace React\Promise;

class RejectedPromise implements ExtendedPromiseInterface, CancellablePromiseInterface
use Interop\Async\Promise as AsyncInteropPromise;

class RejectedPromise implements ExtendedPromiseInterface, CancellablePromiseInterface, AsyncInteropPromise
{
private $reason;

public function __construct($reason = null)
{
if ($reason instanceof PromiseInterface) {
if ($reason instanceof PromiseInterface || $reason instanceof AsyncInteropPromise) {
throw new \InvalidArgumentException('You cannot create React\Promise\RejectedPromise with a promise. Use React\Promise\reject($promiseOrValue) instead.');
}

Expand Down Expand Up @@ -73,4 +75,12 @@ public function progress(callable $onProgress)
public function cancel()
{
}

public function when(callable $onResolved)
{
$onResolved(
UnhandledRejectionException::resolve($this->reason),
null
);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here, needs a try / catch then.

}
}
23 changes: 23 additions & 0 deletions src/functions.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,27 @@

namespace React\Promise;

use Interop\Async\Promise as AsyncInteropPromise;

function resolve($promiseOrValue = null)
{
if ($promiseOrValue instanceof ExtendedPromiseInterface) {
return $promiseOrValue;
}

if ($promiseOrValue instanceof AsyncInteropPromise) {
return new Promise(function ($resolve, $reject) use ($promiseOrValue) {
$promiseOrValue->when(function ($reason = null, $value = null) use ($resolve, $reject) {
if ($reason) {
$reject($reason);
return;
}

$resolve($value);
});
});
}

if (method_exists($promiseOrValue, 'then')) {
$canceller = null;

Expand All @@ -31,6 +46,14 @@ function reject($promiseOrValue = null)
});
}

if ($promiseOrValue instanceof AsyncInteropPromise) {
return new Promise(function ($resolve, $reject) use ($promiseOrValue) {
$promiseOrValue->when(function ($reason = null, $value = null) use ($resolve, $reject) {
$reject($reason ? $reason : $value);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are both null here fine?

Copy link
Member Author

@jsor jsor Dec 22, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I haven't found anything in the specification which defines how the callbacks must be invoked and if none, only $reason or both arguments are required.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The specification states:

Any implementation MUST at least provide these two parameters.

But what I was rather asking here is whether $reject is fine with null being passed?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I just wasn't sure because of UnhandledRejectionException::nullOrResolve. RejectedPromise seems to miss a check for AsyncInteropPromise in __construct btw.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I just wasn't sure because of UnhandledRejectionException::nullOrResolve.

nullOrResolve was actually wrong. In case of rejection without a reason, it would call the when() callbacks with null as first argument signalling success instead of failure. This is fixed in 8073b04.

RejectedPromise seems to miss a check for AsyncInteropPromise in __construct btw.

Good catch, fixed in 8c68ff6

});
});
}

return new RejectedPromise($promiseOrValue);
}

Expand Down
11 changes: 10 additions & 1 deletion tests/FulfilledPromiseTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@
class FulfilledPromiseTest extends TestCase
{
use PromiseTest\PromiseSettledTestTrait,
PromiseTest\PromiseFulfilledTestTrait;
PromiseTest\PromiseFulfilledTestTrait,
PromiseTest\AsyncInteropResolvedTestTrait;

public function getPromiseTestAdapter(callable $canceller = null)
{
Expand Down Expand Up @@ -47,4 +48,12 @@ public function shouldThrowExceptionIfConstructedWithAPromise()

return new FulfilledPromise(new FulfilledPromise());
}

/** @test */
public function shouldThrowExceptionIfConstructedWithAAsyncInteropPromise()
{
$this->setExpectedException('\InvalidArgumentException');

return new FulfilledPromise(new FulfilledPromise());
}
}
37 changes: 37 additions & 0 deletions tests/FunctionRejectTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,24 @@ public function shouldRejectAFulfilledPromise()
);
}

/** @test */
public function shouldRejectAFulfilledAsyncInteropPromise()
{
$resolved = new SimpleFulfilledAsyncInteropTestPromise();

$mock = $this->createCallableMock();
$mock
->expects($this->once())
->method('__invoke')
->with($this->identicalTo('foo'));

reject($resolved)
->then(
$this->expectCallableNever(),
$mock
);
}

/** @test */
public function shouldRejectARejectedPromise()
{
Expand All @@ -61,4 +79,23 @@ public function shouldRejectARejectedPromise()
$mock
);
}

/** @test */
public function shouldRejectARejectedAsyncInteropPromise()
{
$exception = new \Exception('foo');
$resolved = new SimpleRejectedAsyncInteropTestPromise($exception);

$mock = $this->createCallableMock();
$mock
->expects($this->once())
->method('__invoke')
->with($this->identicalTo($exception));

reject($resolved)
->then(
$this->expectCallableNever(),
$mock
);
}
}
37 changes: 37 additions & 0 deletions tests/FunctionResolveTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,24 @@ public function shouldResolveACancellableThenable()
$this->assertTrue($thenable->cancelCalled);
}

/** @test */
public function shouldResolveAFulfilledAsyncInteropPromise()
{
$promise = new SimpleFulfilledAsyncInteropTestPromise();

$mock = $this->createCallableMock();
$mock
->expects($this->once())
->method('__invoke')
->with($this->identicalTo('foo'));

resolve($promise)
->then(
$mock,
$this->expectCallableNever()
);
}

/** @test */
public function shouldRejectARejectedPromise()
{
Expand All @@ -91,6 +109,25 @@ public function shouldRejectARejectedPromise()
);
}

/** @test */
public function shouldRejectARejectedAsyncInteropPromise()
{
$exception = new \Exception('foo');
$promise = new SimpleRejectedAsyncInteropTestPromise($exception);

$mock = $this->createCallableMock();
$mock
->expects($this->once())
->method('__invoke')
->with($this->identicalTo($exception));

resolve($promise)
->then(
$this->expectCallableNever(),
$mock
);
}

/** @test */
public function shouldSupportDeepNestingInPromiseChains()
{
Expand Down
51 changes: 51 additions & 0 deletions tests/PromiseTest/AsyncInteropRejectedTestTrait.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
<?php

namespace React\Promise\PromiseTest;

trait AsyncInteropRejectedTestTrait
{
/**
* @return \React\Promise\PromiseAdapter\PromiseAdapterInterface
*/
abstract public function getPromiseTestAdapter(callable $canceller = null);

public function testWhenOnExceptionFailedAsyncInteropPromise()
{
$adapter = $this->getPromiseTestAdapter();

$adapter->reject(new \RuntimeException);
$adapter->promise()->when(function ($e) use (&$invoked) {
$this->assertSame(get_class($e), "RuntimeException");
$invoked = true;
});
$this->assertTrue($invoked);
}

public function testWhenOnErrorFailedAsyncInteropPromise()
{
if (PHP_VERSION_ID < 70000) {
$this->markTestSkipped("Error only exists on PHP 7+");
}

$adapter = $this->getPromiseTestAdapter();
$adapter->reject(new \Error);
$adapter->promise()->when(function ($e) use (&$invoked) {
$this->assertSame(get_class($e), "Error");
$invoked = true;
});
$this->assertTrue($invoked);
}

public function testWhenOnExceptionFailedAsyncInteropPromiseWhenRejectedWithoutReason()
{
$adapter = $this->getPromiseTestAdapter();

$adapter->reject();
$adapter->promise()->when(function ($e, $value) use (&$invoked) {
$this->assertInstanceOf("Exception", $e);
$this->assertNull($value);
$invoked = true;
});
$this->assertTrue($invoked);
}
}
Loading