Coder Social home page Coder Social logo

promises's Introduction

Guzzle Promises

Promises/A+ implementation that handles promise chaining and resolution iteratively, allowing for "infinite" promise chaining while keeping the stack size constant. Read this blog post for a general introduction to promises.

Features

  • Promises/A+ implementation.
  • Promise resolution and chaining is handled iteratively, allowing for "infinite" promise chaining.
  • Promises have a synchronous wait method.
  • Promises can be cancelled.
  • Works with any object that has a then function.
  • C# style async/await coroutine promises using GuzzleHttp\Promise\Coroutine::of().

Installation

composer require guzzlehttp/promises

Version Guidance

Version Status PHP Version
1.x Bug and security fixes >=5.5,<8.3
2.x Latest >=7.2.5,<8.4

Quick Start

A promise represents the eventual result of an asynchronous operation. The primary way of interacting with a promise is through its then method, which registers callbacks to receive either a promise's eventual value or the reason why the promise cannot be fulfilled.

Callbacks

Callbacks are registered with the then method by providing an optional $onFulfilled followed by an optional $onRejected function.

use GuzzleHttp\Promise\Promise;

$promise = new Promise();
$promise->then(
    // $onFulfilled
    function ($value) {
        echo 'The promise was fulfilled.';
    },
    // $onRejected
    function ($reason) {
        echo 'The promise was rejected.';
    }
);

Resolving a promise means that you either fulfill a promise with a value or reject a promise with a reason. Resolving a promise triggers callbacks registered with the promise's then method. These callbacks are triggered only once and in the order in which they were added.

Resolving a Promise

Promises are fulfilled using the resolve($value) method. Resolving a promise with any value other than a GuzzleHttp\Promise\RejectedPromise will trigger all of the onFulfilled callbacks (resolving a promise with a rejected promise will reject the promise and trigger the $onRejected callbacks).

use GuzzleHttp\Promise\Promise;

$promise = new Promise();
$promise
    ->then(function ($value) {
        // Return a value and don't break the chain
        return "Hello, " . $value;
    })
    // This then is executed after the first then and receives the value
    // returned from the first then.
    ->then(function ($value) {
        echo $value;
    });

// Resolving the promise triggers the $onFulfilled callbacks and outputs
// "Hello, reader."
$promise->resolve('reader.');

Promise Forwarding

Promises can be chained one after the other. Each then in the chain is a new promise. The return value of a promise is what's forwarded to the next promise in the chain. Returning a promise in a then callback will cause the subsequent promises in the chain to only be fulfilled when the returned promise has been fulfilled. The next promise in the chain will be invoked with the resolved value of the promise.

use GuzzleHttp\Promise\Promise;

$promise = new Promise();
$nextPromise = new Promise();

$promise
    ->then(function ($value) use ($nextPromise) {
        echo $value;
        return $nextPromise;
    })
    ->then(function ($value) {
        echo $value;
    });

// Triggers the first callback and outputs "A"
$promise->resolve('A');
// Triggers the second callback and outputs "B"
$nextPromise->resolve('B');

Promise Rejection

When a promise is rejected, the $onRejected callbacks are invoked with the rejection reason.

use GuzzleHttp\Promise\Promise;

$promise = new Promise();
$promise->then(null, function ($reason) {
    echo $reason;
});

$promise->reject('Error!');
// Outputs "Error!"

Rejection Forwarding

If an exception is thrown in an $onRejected callback, subsequent $onRejected callbacks are invoked with the thrown exception as the reason.

use GuzzleHttp\Promise\Promise;

$promise = new Promise();
$promise->then(null, function ($reason) {
    throw new Exception($reason);
})->then(null, function ($reason) {
    assert($reason->getMessage() === 'Error!');
});

$promise->reject('Error!');

You can also forward a rejection down the promise chain by returning a GuzzleHttp\Promise\RejectedPromise in either an $onFulfilled or $onRejected callback.

use GuzzleHttp\Promise\Promise;
use GuzzleHttp\Promise\RejectedPromise;

$promise = new Promise();
$promise->then(null, function ($reason) {
    return new RejectedPromise($reason);
})->then(null, function ($reason) {
    assert($reason === 'Error!');
});

$promise->reject('Error!');

If an exception is not thrown in a $onRejected callback and the callback does not return a rejected promise, downstream $onFulfilled callbacks are invoked using the value returned from the $onRejected callback.

use GuzzleHttp\Promise\Promise;

$promise = new Promise();
$promise
    ->then(null, function ($reason) {
        return "It's ok";
    })
    ->then(function ($value) {
        assert($value === "It's ok");
    });

$promise->reject('Error!');

Synchronous Wait

You can synchronously force promises to complete using a promise's wait method. When creating a promise, you can provide a wait function that is used to synchronously force a promise to complete. When a wait function is invoked it is expected to deliver a value to the promise or reject the promise. If the wait function does not deliver a value, then an exception is thrown. The wait function provided to a promise constructor is invoked when the wait function of the promise is called.

$promise = new Promise(function () use (&$promise) {
    $promise->resolve('foo');
});

// Calling wait will return the value of the promise.
echo $promise->wait(); // outputs "foo"

If an exception is encountered while invoking the wait function of a promise, the promise is rejected with the exception and the exception is thrown.

$promise = new Promise(function () use (&$promise) {
    throw new Exception('foo');
});

$promise->wait(); // throws the exception.

Calling wait on a promise that has been fulfilled will not trigger the wait function. It will simply return the previously resolved value.

$promise = new Promise(function () { die('this is not called!'); });
$promise->resolve('foo');
echo $promise->wait(); // outputs "foo"

Calling wait on a promise that has been rejected will throw an exception. If the rejection reason is an instance of \Exception the reason is thrown. Otherwise, a GuzzleHttp\Promise\RejectionException is thrown and the reason can be obtained by calling the getReason method of the exception.

$promise = new Promise();
$promise->reject('foo');
$promise->wait();

PHP Fatal error: Uncaught exception 'GuzzleHttp\Promise\RejectionException' with message 'The promise was rejected with value: foo'

Unwrapping a Promise

When synchronously waiting on a promise, you are joining the state of the promise into the current state of execution (i.e., return the value of the promise if it was fulfilled or throw an exception if it was rejected). This is called "unwrapping" the promise. Waiting on a promise will by default unwrap the promise state.

You can force a promise to resolve and not unwrap the state of the promise by passing false to the first argument of the wait function:

$promise = new Promise();
$promise->reject('foo');
// This will not throw an exception. It simply ensures the promise has
// been resolved.
$promise->wait(false);

When unwrapping a promise, the resolved value of the promise will be waited upon until the unwrapped value is not a promise. This means that if you resolve promise A with a promise B and unwrap promise A, the value returned by the wait function will be the value delivered to promise B.

Note: when you do not unwrap the promise, no value is returned.

Cancellation

You can cancel a promise that has not yet been fulfilled using the cancel() method of a promise. When creating a promise you can provide an optional cancel function that when invoked cancels the action of computing a resolution of the promise.

API

Promise

When creating a promise object, you can provide an optional $waitFn and $cancelFn. $waitFn is a function that is invoked with no arguments and is expected to resolve the promise. $cancelFn is a function with no arguments that is expected to cancel the computation of a promise. It is invoked when the cancel() method of a promise is called.

use GuzzleHttp\Promise\Promise;

$promise = new Promise(
    function () use (&$promise) {
        $promise->resolve('waited');
    },
    function () {
        // do something that will cancel the promise computation (e.g., close
        // a socket, cancel a database query, etc...)
    }
);

assert('waited' === $promise->wait());

A promise has the following methods:

  • then(callable $onFulfilled, callable $onRejected) : PromiseInterface

    Appends fulfillment and rejection handlers to the promise, and returns a new promise resolving to the return value of the called handler.

  • otherwise(callable $onRejected) : PromiseInterface

    Appends a rejection handler callback to the promise, and returns a new promise resolving to the return value of the callback if it is called, or to its original fulfillment value if the promise is instead fulfilled.

  • wait($unwrap = true) : mixed

    Synchronously waits on the promise to complete.

    $unwrap controls whether or not the value of the promise is returned for a fulfilled promise or if an exception is thrown if the promise is rejected. This is set to true by default.

  • cancel()

    Attempts to cancel the promise if possible. The promise being cancelled and the parent most ancestor that has not yet been resolved will also be cancelled. Any promises waiting on the cancelled promise to resolve will also be cancelled.

  • getState() : string

    Returns the state of the promise. One of pending, fulfilled, or rejected.

  • resolve($value)

    Fulfills the promise with the given $value.

  • reject($reason)

    Rejects the promise with the given $reason.

FulfilledPromise

A fulfilled promise can be created to represent a promise that has been fulfilled.

use GuzzleHttp\Promise\FulfilledPromise;

$promise = new FulfilledPromise('value');

// Fulfilled callbacks are immediately invoked.
$promise->then(function ($value) {
    echo $value;
});

RejectedPromise

A rejected promise can be created to represent a promise that has been rejected.

use GuzzleHttp\Promise\RejectedPromise;

$promise = new RejectedPromise('Error');

// Rejected callbacks are immediately invoked.
$promise->then(null, function ($reason) {
    echo $reason;
});

Promise Interoperability

This library works with foreign promises that have a then method. This means you can use Guzzle promises with React promises for example. When a foreign promise is returned inside of a then method callback, promise resolution will occur recursively.

// Create a React promise
$deferred = new React\Promise\Deferred();
$reactPromise = $deferred->promise();

// Create a Guzzle promise that is fulfilled with a React promise.
$guzzlePromise = new GuzzleHttp\Promise\Promise();
$guzzlePromise->then(function ($value) use ($reactPromise) {
    // Do something something with the value...
    // Return the React promise
    return $reactPromise;
});

Please note that wait and cancel chaining is no longer possible when forwarding a foreign promise. You will need to wrap a third-party promise with a Guzzle promise in order to utilize wait and cancel functions with foreign promises.

Event Loop Integration

In order to keep the stack size constant, Guzzle promises are resolved asynchronously using a task queue. When waiting on promises synchronously, the task queue will be automatically run to ensure that the blocking promise and any forwarded promises are resolved. When using promises asynchronously in an event loop, you will need to run the task queue on each tick of the loop. If you do not run the task queue, then promises will not be resolved.

You can run the task queue using the run() method of the global task queue instance.

// Get the global task queue
$queue = GuzzleHttp\Promise\Utils::queue();
$queue->run();

For example, you could use Guzzle promises with React using a periodic timer:

$loop = React\EventLoop\Factory::create();
$loop->addPeriodicTimer(0, [$queue, 'run']);

Implementation Notes

Promise Resolution and Chaining is Handled Iteratively

By shuffling pending handlers from one owner to another, promises are resolved iteratively, allowing for "infinite" then chaining.

<?php
require 'vendor/autoload.php';

use GuzzleHttp\Promise\Promise;

$parent = new Promise();
$p = $parent;

for ($i = 0; $i < 1000; $i++) {
    $p = $p->then(function ($v) {
        // The stack size remains constant (a good thing)
        echo xdebug_get_stack_depth() . ', ';
        return $v + 1;
    });
}

$parent->resolve(0);
var_dump($p->wait()); // int(1000)

When a promise is fulfilled or rejected with a non-promise value, the promise then takes ownership of the handlers of each child promise and delivers values down the chain without using recursion.

When a promise is resolved with another promise, the original promise transfers all of its pending handlers to the new promise. When the new promise is eventually resolved, all of the pending handlers are delivered the forwarded value.

A Promise is the Deferred

Some promise libraries implement promises using a deferred object to represent a computation and a promise object to represent the delivery of the result of the computation. This is a nice separation of computation and delivery because consumers of the promise cannot modify the value that will be eventually delivered.

One side effect of being able to implement promise resolution and chaining iteratively is that you need to be able for one promise to reach into the state of another promise to shuffle around ownership of handlers. In order to achieve this without making the handlers of a promise publicly mutable, a promise is also the deferred value, allowing promises of the same parent class to reach into and modify the private properties of promises of the same type. While this does allow consumers of the value to modify the resolution or rejection of the deferred, it is a small price to pay for keeping the stack size constant.

$promise = new Promise();
$promise->then(function ($value) { echo $value; });
// The promise is the deferred value, so you can deliver a value to it.
$promise->resolve('foo');
// prints "foo"

Upgrading from Function API

A static API was first introduced in 1.4.0, in order to mitigate problems with functions conflicting between global and local copies of the package. The function API was removed in 2.0.0. A migration table has been provided here for your convenience:

Original Function Replacement Method
queue Utils::queue
task Utils::task
promise_for Create::promiseFor
rejection_for Create::rejectionFor
exception_for Create::exceptionFor
iter_for Create::iterFor
inspect Utils::inspect
inspect_all Utils::inspectAll
unwrap Utils::unwrap
all Utils::all
some Utils::some
any Utils::any
settle Utils::settle
each Each::of
each_limit Each::ofLimit
each_limit_all Each::ofLimitAll
!is_fulfilled Is::pending
is_fulfilled Is::fulfilled
is_rejected Is::rejected
is_settled Is::settled
coroutine Coroutine::of

Security

If you discover a security vulnerability within this package, please send an email to [email protected]. All security vulnerabilities will be promptly addressed. Please do not disclose security-related issues publicly until a fix has been announced. Please see Security Policy for more information.

License

Guzzle is made available under the MIT License (MIT). Please see License File for more information.

For Enterprise

Available as part of the Tidelift Subscription

The maintainers of Guzzle and thousands of other packages are working with Tidelift to deliver commercial support and maintenance for the open source dependencies you use to build your applications. Save time, reduce risk, and improve code health, while paying the maintainers of the exact dependencies you use. Learn more.

promises's People

Contributors

alexeyshockov avatar andont avatar ansionfor avatar baileylo avatar bpolaszek avatar ediporeboucas avatar enleur avatar erikn69 avatar gmponos avatar grahamcampbell avatar ikeyan avatar imme-emosol avatar itafroma avatar jeremeamia avatar jeskew avatar jimcottrell avatar joshdifabio avatar kicken avatar mcfedr avatar mtdowling avatar ntzm avatar nyholm avatar reedy avatar sagikazarmark avatar stevenwadejr avatar suzuki avatar timwolla avatar tobion avatar villfa avatar yched avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

promises's Issues

Can I manipulate the response body on fulfilled promise with a callback?

here's my example:

// Build Asynchronous Promise
$promise = $this->client->requestAsync($this->method, $path, $options);

// Extract target data from response JSON after request fulfilled
$promise->then(
    function ($response) {
        $result = GuzzleResponseParser::parse($response);
        $extracted = $this->getTargetDataFromResult($result);

        //This dumps the right data
        //var_dump($extracted);
        //exit();

        // This, however, does not work as I'd expect.  It doesn't error, but see notes below.                 
        return $extracted;
    }
);

I am later using

$results = Promise\unwrap($promises);
foreach ($results as $key => $result) {
    $this->data[$key] = $result;
}

I was hoping the $extracted in the then() closure would translate to the $result in the post-unwrap loop, but this does not happen. I see the regular result, not the extracted result.

Am I going about this wrong? I've reviewed documentation and can't seem to find what I need. Might the be another way to do this, or is it not possible? Thanks in advance!

Coroutine issue with 1.3.0 release

Latest 1.3.0 release includes coroutine changes that seems break aws-sdk-php behaviors.
coroutine is used in our instanceCredentialProvider, which is chained in our defaultCredentialProvider

This problem is related to guzzle because there has been no related code changes in our repo, and when downgrade to 1.2.0 fix the problem.

Personally, I'm having trouble in reproducing the issue locally or with a new ubuntu ec2 instance, and I'm still woking on reproducing it since many customer have reported seeing this issue happening.

Related issue:
aws/aws-sdk-php#1125
guzzle/guzzle#1668

Possible reproduce repo: https://github.com/reproduce/guzzle/tree/issue1668

Open this issue here as well just to have more attention, background and thoughts here. :)

Dynamic promise stack

Hello,

TL; DR

Guzzle's all($promises) function should be recursive: resolution of $promises may add new promises into the $promises stack. These new promises should not be left pending.

Example

I'm using Guzzle's all() function to resolve a collection of promises, stored in an iterator.
What if one of these promises, when resolved, adds a new promise to the stack?

Have a look at the following code:

$data       = new ArrayObject();
$promises   = new ArrayIterator();
$promises[] = $fooPromise = new \GuzzleHttp\Promise\Promise(function () use ($promises, &$fooPromise, $data) {
    var_dump('Loading foo');
    $data[] = 'foo';
    $fooPromise->resolve('foo');
});

$fooPromise->then(function () use (&$promises, $data) {

    # When $fooPromise is resolved, for some reason I may add some data with another promise
    $barPromise = new \GuzzleHttp\Promise\Promise(function () use ($promises, &$barPromise, $data) {
        var_dump('Loading bar');
        $data[] = 'bar';
        $barPromise->resolve('bar');
    });

    $promises[] = $barPromise;

});

$mainPromise = \GuzzleHttp\Promise\all($promises);
$mainPromise->wait();
var_dump($data->getArrayCopy());

I expect the following output:

string(11) "Loading foo"
string(11) "Loading bar"
array(2) {
  [0]=>
  string(3) "foo"
  [1]=>
  string(3) "bar"
}

But I get this:

string(11) "Loading foo"
array(1) {
  [0]=>
  string(3) "foo"
}

And my $secondPromise state is pending. Why is it not resolved?

Thank you,
Ben

Error in the documentation

Hello,

there is an error in the documentation. The last piece of shown code refers to the deliver method, but this method does not exist in the whole Promises code base:

$promise = new Promise();
$promise->then(function ($value) { echo $value; });
// The promise is the deferred value, so you can deliver a value to it.
$promise->deliver('foo');

This code is broken because the deliver method is not defined, and will throw a PHP fatal error, but we can find this example in the documentation.

Tag a new release

Please tag a new release so that there's a stable version with Throwable support.

Promises callbacks not called until shutdown

Hi

From the guide in the readme:

use GuzzleHttp\Promise\Promise;

$promise = new Promise();
$promise
    ->then(function ($value) {
        // Return a value and don't break the chain
        return "Hello, " . $value;
    })
    // This then is executed after the first then and receives the value
    // returned from the first then.
    ->then(function ($value) {
        echo $value;
    });

// Resolving the promise triggers the $onFulfilled callbacks and outputs
// "Hello, reader".
$promise->resolve('reader.');

Should output "Hello, reader".

However, this doesn't happen until it appears a shutdown handler is called: https://github.com/guzzle/promises/blob/master/src/TaskQueue.php#L21 or the waitIfPending() method is called.

If a promise is resolved manually, then the callbacks DO NOT fire. I can't seem to work out if this is intentional or a bug, it doesn't make sense that the handlers are called only in one scenario of waiting for a promise.

Can someone explain.

Thanks

resolve() cannot omit the value.

the thenable cannot omit the value for resolve().

PHP Fatal error:  Uncaught ArgumentCountError: Too few arguments to function GuzzleHttp\Promise\Promise::resolve(), 0 passed

but it's very useful for just resolve the promise without returning the value.
For ReactPHP and nodejs, we can omit the resolve value and just resolve the promise directly.

Event loop integration makes bad recommendation

It suggests the following as event loop integration for ReactPHP:

$loop = React\EventLoop\Factory::create();
$loop->addPeriodicTimer(0, [$queue, 'run']);

That will basically be an infinite loop that always calls [$queue, 'run'] and hogs the CPU until the entire thing is done. A better way would be a short timer like 10 milliseconds. It's not as responsive, but at least it doesn't consume 100% CPU.

Using "then" for determining whether an argument is a promise.

Hi,

I'd just like to make a request about this bit of code in FulfilledPromise:

        if (method_exists($value, 'then')) {
            throw new \InvalidArgumentException(
                'You cannot create a FulfilledPromise with a promise.');
        }

Is there any chance you could change that first line to:

      if ($value instanceof 'GuzzleHttp\Promise\PromiseInterface')){

I'm trying to pass a non-promise object as an argument, and it's bailing because my object has a 'then' method.

Example

Provide some good example working one...

Promise not working as async without wait()

The code works if i do $promise->wait();, however i am not sure how can i call the promise to work async.

foreach ($entries as $key=>$value)
        {     
                $promise = new Promise(function() use (&$promise, $key, $value) 
                {   
                    // Do some heavy lifting work.
                    $mainOutput = array($key, $output, $value);
                    $promise->resolve($mainOutput);
                });

                $promise->then(
                    // $onFulfilled
                    function ($mainOutput)
                    {
                        static::addToResponse($mainOutput);
                    },
                    // $onRejected
                    function ($reason) 
                    {
                        echo 'The promise was rejected.';
                    }
                );
            if(static::$sync)  
            {    
                $promise->wait();
            }
            else
            {

            }
        }

deliver method used in README.md

In README.md in a few examples the method deliver is used. This method is not available anymore. Maybe the examples should use resolve now? I saw that deliver was a private method in early commits. (Would the examples work with a private method?)

cancel() not bubbling up to recursive promises?

The following code yields no response. Shouldn't it trigger the cancel() method in the inner promise? Is this a bug or a feature?

$promise = new Promise();
$promise->resolve(true);
$promise
    ->then(function() {
        return (new Promise())
            ->then(function() {
                echo "inner-promise fulfilled!";
            })
            ->otherwise(function() {
                echo "inner-promise rejected!";
            });
    })
    ->then(function() {
        echo "promise fulfilled!";
    })
    ->otherwise(function() {
        echo "promise rejected!";
    })
    ->cancel();

\GuzzleHttp\Promise\queue()->run();

Producer -> consumer chain?

I don't really understand can I implement producer-consumer scheme using this library?

For example, imagine a producing function which makes some values over time, and another consuming function which takes the values. Is there a way to implement this using Promises?

reactphp/promise

How can I convert guzzle/promises to reactphp/promise ?

// $guzzlePromise is an instance of GuzzleHttp\Promise\Promise and is a result of third party library
$reactPromise = convert($guzzlePromise);

Uncaught Promises should throw Exception

I've noticed (#73) that uncaught errors do not throw exceptions, this can make debugging really difficult because there isn't an obvious reason of why a promise chain is breaking.

I believe in JavaScript, an uncaught promise error throws a fatal error, so I think throwing an exception (outside of the promise chain) for uncaught promises would be a good idea here.

Please tag a version 1.0.2

I do need that feature of conditionally loading the global functions due to messed up autoloading on my side.

Throwing Exception from within promises handler doesn't stop script execution

I'm doing some integration testing with a library and use guzzle to make an async http request and noticed that my tests were always passing even when that is not expected. Through debugging narrowed down the issue to being caused by the fact that an exception thrown from a then in a promise won't stop the system from running.

In the snippet bellow for instance, I'm able to get the log in the error.log but PHPUnit never gets the exception when running the unit test. I also tried running this in isolation outside of the unit testing context and same behavior is exhibited

$promise = $httpClient->requestAsync('GET', 'http://some-api-url');

$promise->then(
    function ($response) {
        error_log('Exceptions thrown in promise do not bubble-up');
        throw new Exception('Invalid hit sent. got response: ' . $response->getBody());
    },
    function ($exception) {
        // handle HTTP request exception
    }
);

Is this behavior expected? Am I doing something wrong?

Thanks

Promise chain breaks if then() returns a value

If you have something like this:

$client = new Client();
$client->requestAsync('GET', 'http://example.com')->then(function($result) {
  // This executes.
  return 'Hello';
})->then(function($value) {
  // This executes as well.
  return $value . ' World!';
})->then(function($value) {
  // This never executes.
  return 'Never gonna give you up';
});

The reason for this is (from what I can tell) because Guzzle wraps the first handler in a promise, but it doesn't wrap anything else because this library does not wrap a then() value.

I think it should be consistent either way, it was hard to debug why this was happening. Wrapping the second then() in a new FullfilledPromise() like this:

return new FullfilledPromise($value . ' World!');

fixed my problem.

I expected a value return in then() to be wrapped in a promise all the time, so I think that's what should happen and it should be a trivial change and I don't think it should break anyone's apps (except for that promises that weren't ever executed will now be executed).

Promise resolution and reference

Hi everyone,

TL; DR

Make promise resolution simpler:

$promise = new \GuzzleHttp\Promise\Promise(function () use (&$promise) {
    $promise->resolve('The referenced promise has been resolved');
});

to:

$promise = new \GuzzleHttp\Promise\Promise(function (PromiseInterface $promise) {
    $promise->resolve('The promise has been resolved');
});

Example

Actually, when you create a promise from scratch you must use a reference to that promise within the wait function, i.e.:

$promise = new \GuzzleHttp\Promise\Promise(function () use (&$promise) {
    $promise->resolve('The referenced promise has been resolved');
});

From a DX point of view, this can lead to headaches when it comes to generate promises within a loop. Let's take this as an example:

use GuzzleHttp\Promise\Promise;
use function GuzzleHttp\Promise\all;

require_once __DIR__ . '/vendor/autoload.php';

$translationMap = [
    'banana' => 'banane',
    'apple'  => 'pomme',
];

$fruits = [
    'banana',
];

$promises = [];

foreach ($fruits AS $fruit) {
    $promises[$fruit] = $promise = new Promise(function () use (&$promise, $translationMap, $fruit) {
        $promise->resolve($translationMap[$fruit]);
    });
}

var_dump(all($promises)->wait()); // Expected ['banana' => 'banane'] and indeed got ['banana' => 'banane']

In this example, there's only 1 instance of $promise, and everything's fine. Now add an apple into the $fruits array:

use GuzzleHttp\Promise\Promise;
use function GuzzleHttp\Promise\all;

require_once __DIR__ . '/vendor/autoload.php';

$translationMap = [
    'banana' => 'banane',
    'apple'  => 'pomme',
];

$fruits = [
    'banana',
    'apple',
];

$promises = [];

foreach ($fruits AS $fruit) {
    $promises[$fruit] = $promise = new Promise(function () use (&$promise, $translationMap, $fruit) {
        $promise->resolve($translationMap[$fruit]);
    });
}

var_dump(all($promises)->wait()); // Expected ['banana' => 'banane', 'apple' => 'pomme'] 
// But got GuzzleHttp\Promise\RejectionException: Invoking the wait callback did not resolve the promise...

Of course, this RejectionException would not happen if I chose a different variable name for each $promise. But honestly this is ugly.

A simpler solution would be to pass the promise itself directly as an argument into the wait function, instead of relying on a reference, i.e:

$promise = new \GuzzleHttp\Promise\Promise(function (PromiseInterface $promise) {
    $promise->resolve('The promise has been resolved');
});

This would require a change into the Promise::invokeWaitFn() method:

$wfn($this);
// instead of
$wfn(true);

This would simplify a lot the promise resolution.
What's your opinion ?

Allow throwing an exception to actually propagate

Hey there,

I've got the following code:

$promise->then(
                // $onFulfilled
                function ($user) {
                   // bla bla
                },
                // $onRejected
                function ($reason) {
                    throw new Error('reset_password_token_expired');
                }
            );

but the error is being caught.

What can I do to allow it to propagate up the error chain and crash the app?

Thank you

General question

I can't seem to find a "core" answer that is really important to me, sorry if obvious/written but how does a core promise object can work async? Does it pops a new php process on the server or separate thread?
Thks for the hints.

Asynchronous sleep

I was building some async code that triggers a delayed retry in the case of an error, similar to:

$promise = $client->getAsync(...);

$promise->then(
  function ($result) { ... },
  function ($error) {
    sleep(5); 
    $client->getAsync(...);
  }
);

Of course, sleep(5) is blocking. Is there an asynchronous equivalent for sleep, such as a promise that will resolve within a wait() call after the given amount of time? If not, can one be added to the library?

Done instead of Then

Please see https://github.com/reactphp/promise#done-vs-then
How can I have "done" in guzzle/promises?

I need this feature in Event Loop Integration

Calling done() transfers all responsibility for errors to your code. If an error (either a thrown exception or returned rejection) escapes the $onFulfilled or $onRejected callbacks you provide to done, it will be rethrown in an uncatchable way causing a fatal error.

Omit the parameter in the callback function of then/otherwise

This is not an issue, I just wanted to get your opinion on a topic.

To resolve a promise we do

$promise->then(function[...]

The callback parameter gets the ResponseInterface $response as function parameter. What if I don't want to use that value? If I just want to log something, or dunno, do something else which has nothing to do with the response. Do you think it's good practice to do $promise->then(function () or would you always write $promise->then(function (ResponseInterface $response)) even though you won't need that parameter?

Was just wondering, because omitting the parameter would mean, that we are getting a warning about calling a method with no parameter, right?

[DOUBT] Problems implementing a "wrapper" promise

Hello,

I'm implementing a DB client for a database using HTTP as its wire protocol. I'm trying to implement a wrapper for the promises returned by the Guzzle\Client, but it's clear that I've misunderstood something about this library ^_^ .

Before starting delving into details, my problem seems to be simple: In theory my code is pretending to "catch" Guzzle's exceptions and wrapping it with my own class. What is happening is the Guzzle exception is thrown at some point which I can't identify, and remains uncaught.

Here you can see my code, full of debug prints:

<?php


namespace Druid4PHP\Responses;


use Druid4PHP\Errors\DruidRequestError;
use Druid4PHP\Errors\DruidServerError;
use Druid4PHP\Errors\DruidResponseParseError;
use Druid4PHP\Queries\Query;

use GuzzleHttp\Promise\Promise;
use GuzzleHttp\Promise\PromiseInterface;

use Psr\Http\Message\ResponseInterface;


/**
 * @method Response|array wait($unwrap = true)
 */
final class AsyncResponse extends Promise
{
    /** @var Query */
    private $query;

    /** @var PromiseInterface */
    private $httpPromise;

    /** @var bool */
    private $encapsulation;

    /**
     * AsyncResponse constructor.
     * @param Query $query
     * @param PromiseInterface $httpPromise
     * @param bool $encapsulation
     */
    public function __construct(Query $query, PromiseInterface $httpPromise, $encapsulation = true)
    {
        echo "\nINSIDE __construct\n";

        $this->query = $query;
        $this->httpPromise = $httpPromise->then(
            function (ResponseInterface $response) {
                echo "\nIN A FEW nanos WE'LL GET INTO this->onFulfilledHttpPromise\n";
                return $this->onFulfilledHttpPromise($response);
            },
            function (\Exception $e) {
                echo "\nIN A FEW nanos WE'LL GET INTO this->onRejectedHttpPromise\n";
                return $this->onRejectedHttpPromise($e);
            }
        );
        $this->encapsulation = $encapsulation;

        parent::__construct(
            function () {
                echo "\nIN A FEW nanos WE'LL GET INTO this->waitHandler\n";
                return $this->waitHandler();
            },
            function () {
                echo "\nIN A FEW nanos WE'LL GET INTO this->cancelHandler\n";
                return $this->cancelHandler();
            }
        );

        echo "\nGOING OUT OF __construct\n";
    }

    private function waitHandler()
    {
        echo "\nNOW WE'LL CALL httpPromise->wait\n";

        $this->httpPromise->wait();

        echo "\nNOW WE'VE CALLED httpPromise->wait\n\n";
    }

    private function cancelHandler()
    {
        echo "\nNOW WE'LL CALL httpPromise->cancel\n";

        $this->httpPromise->cancel();

        echo "\nNOW WE'VE CALLED httpPromise->cancel\n";
    }

    private function onFulfilledHttpPromise(ResponseInterface $response)
    {
        echo "\nINSIDE onFulfilledHttpPromise\n";

        // TODO: allow stream decoding without having to call getContents.
        $rawResponse = $response->getBody()->getContents();
        $rawResults = json_decode($rawResponse, true);

        if (JSON_ERROR_NONE !== json_last_error()) {
            throw new DruidResponseParseError($this->query, $rawResponse, 'Received invalid JSON');
        }

        echo "\nIN A FEW nanos WE'll GO INSIDE this->resolve\n";

        $this->resolve(
            $this->encapsulation ? $this->query->parseResponse($rawResults) : $rawResults
        );

        echo "\nGOING OUT OF onFulfilledHttpPromise\n";
    }

    private function onRejectedHttpPromise(\Exception $e)
    {
        echo "\nINSIDE onRejectedHttpPromise (".get_class($this).")\n";

        $eCode = $e->getCode();
        if ($eCode >= 500 && $eCode < 600) {
            $this->reject(new DruidServerError($this->query, $e));
        } else {
            $this->reject(new DruidRequestError($this->query, $e));
        }

        echo "\nGOING OUT OF onRejectedHttpPromise\n";
    }
}

and here you can see the output of one of my executions:


Starting with timeseries queries:

INSIDE __construct

GOING OUT OF __construct

IN A FEW nanos WE'LL GET INTO this->waitHandler

NOW WE'LL CALL httpPromise->wait

IN A FEW nanos WE'LL GET INTO this->onRejectedHttpPromise

INSIDE onRejectedHttpPromise (Druid4PHP\Responses\AsyncResponse)

GOING OUT OF onRejectedHttpPromise

NOW WE'VE CALLED httpPromise->wait

PHP Fatal error:  Uncaught exception 'GuzzleHttp\Exception\ServerException' with message 'Server error: `POST http://127.0.0.1:8082/druid/v2/` resulted in a `500 Internal Server Error` response:
{"error":"Instantiation of [simple type, class io.druid.query.timeseries.TimeseriesQuery] value failed: Missing fields [ (truncated...)
' in /home/acorrea/PhpstormProjects/Druid4PHP/vendor/guzzlehttp/guzzle/src/Exception/RequestException.php:107
Stack trace:
#0 /home/acorrea/PhpstormProjects/Druid4PHP/vendor/guzzlehttp/guzzle/src/Middleware.php(65): GuzzleHttp\Exception\RequestException::create(Object(GuzzleHttp\Psr7\Request), Object(GuzzleHttp\Psr7\Response))
#1 /home/acorrea/PhpstormProjects/Druid4PHP/vendor/guzzlehttp/promises/src/Promise.php(201): GuzzleHttp\Middleware::GuzzleHttp\{closure}(Object(GuzzleHttp\Psr7\Response))
#2 /home/acorrea/PhpstormProjects/Druid4PHP/vendor/guzzlehttp/promises/src/Promise.php(154): GuzzleHttp\Promise\Promise::callHandler(1, Object(GuzzleHttp\Psr7\Response), Array)
#3 /home/acorrea/PhpstormProjects/ in /home/acorrea/PhpstormProjects/Druid4PHP/src/Responses/AsyncResponse.php on line 116

what is surprising to me is the exception is being thrown after this piece if code ( $this->httpPromise->wait(); ) gets executed, and not during its execution.

Any idea to fix my implementation?

Thank you in advance.

Unhandled promise

I think it's good that we have a method like set_exception_handler for promise that sets a user-defined unhandled promise rejection handler function

How to unit test a promise?

Disclaimer: This is not a bug, this is just a question.

I have created a web client for a particular API. I also created a Middleware for handling exceptions, the code looks like this:

class ErrorHandlerMiddleware {
    public static function create() {
        return function (callable $handler) {
            return function ($request, array $options) use ($handler) {
                if ($options['http_errors']) {
                    return $handler($request, $options);
                }

                return $handler($request, $options)->then(
                    function (ResponseInterface $response) use ($request, $handler) {
                        $code = $response->getStatusCode();
                        if ($code < 400) {
                            return $response;
                        }

                        switch ($code) {
                            // ....
                            case '401':
                                throw new AccessDeniedException('Access Denied', $request, $response);
                            //...
                        }

                    },
                    function (RequestException $e) use ($request, $handler) {
                        throw new ConnectException('Service not working', $request);
                    }
                );
            };
        };
    }
}

My testing strategy is to create a dummy response object and set the status code to say 401 and then assert that an AccessDeniedException is thrown.

This is what I've done in my unit test

public function testFoo() {
    $options = [
        'http_errors' => false
    ];
    $request = new \GuzzleHttp\Psr7\Request('get', 'http://foo.com');
    $handler = function($request, $options) {
        return new GuzzleHttp\Promise\Promise();
    };

    $middleware = ErrorHandlerMiddleware::create();
    $request_processor = $middleware($handler);

    $promise = $request_processor($request, $options);
}

According to the documentation I need to "resolve" the promise so that I get my callback.

Question 1: How do I use my dummy $response object with the callback defined in the promise?

I tried something like this

$resolved = $promise->resolve('return new \GuzzleHttp\Psr7\Response(200)');
var_dump($resolved());

But I get this error message: "Function name must be a string"

Question 2: It seems like "resolve" only accepts strings, if that's the case, How Am I supposed to use my $response object inside the promise?

$onFulfilled and $onRejected functions are never called in a REPL

Running this example code you wrote in a REPL (with php -a) will never output the value string:

use GuzzleHttp\Promise\FulfilledPromise;

$promise = new FulfilledPromise('value');

// Fulfilled callbacks are immediately invoked.
$promise->then(function ($value) {
    echo $value;
});

I tried to write some data in a file instead of writing on the standard output, but the issue is the same: the $onFulfilled and $onRejected functions are never called while it works perfectly in a dedicated PHP file.

This issue also occurs with Psy Shell.

How promise is async

It runs promise callbacks on php script shutdown.. So all the promises will be executed one by one..

i want to know if it really works parallel

ReactTaskQueue in the core

I think it's useful to have task queue adapters (TaskQueueInterface implementations) for popular event loops in the core, such as ReactPHP event loop.

Unit testing

Hi,

I'm attempting to do some unit testing using a class that utilizes the promises library. Here's the general approach (in a file MyTest.php):

use PHPUnit\Framework\TestCase;
use GuzzleHttp\Promise\FulfilledPromise;

class MyTest extends TestCase {

  /**
   * Tests unit testing with callbacks.
   */
  public function testFoo() {
    $p = new FulfilledPromise("");
    $me = $this;
    $p->then(function() use ($me) {
        $me->assertTrue(TRUE, "Foo promise returning method succeeded.");
        print (sprintf("%s::%s line %s: Success callback executed\n", get_class($this), __FUNCTION__, __LINE__));
    }, function() use ($me) {
        $me->assertTrue(FALSE, "Foo promise returning method succeeded.");
        print (sprintf("%s::%s line %s: Failure callback executed\n", get_class($this), __FUNCTION__, __LINE__));
    });
  }
}

The test completes without any assertions occurring (at least, none whose output is recognized by the testing suite). It appears the callbacks execute after the unit test completes:

PHPUnit 5.2.9 by Sebastian Bergmann and contributors.

Runtime:       PHP 5.6.18 with Xdebug 2.3.3
Configuration: [excluded]

.                                                                   1 / 1 (100%)

Time: 154 ms, Memory: 5.25Mb

OK (1 test, 0 assertions)
MyTest::{closure} line 91: Success callback executed

Do you know of a decent approach to using PHPUnit with Guzzle promises?

Promises problem in PHPUnit

I have a problem with my Bearer-Authorization in Guzzle-HTTP. I use it to test my PHP-REST-API with PHPUnit.
Here is my test method:

public function testGetMe()
{
$client = new Client([
'base_uri' => $this->apiBaseURL
]);
$data = ['email' => $email, 'password' => '12345'];
$client->post('register', [
'form_params' => $data]
);
$responseJson = json_decode($response->getBody());
$myToken = $responseJson->data->token;

$response = $client->request('GET', 'users', [
    'headers' => [
        'Authorization'      => 'Bearer '.$myToken
    ],
    'debug' => true
]);

}
`

But if I set the token hard coded like this:
`
public function testGetMe()
{
$client = new Client([
'base_uri' => $this->apiBaseURL
]);
$data = ['email' => $email, 'password' => '12345'];
$client->post('register', [
'form_params' => $data]
);
$responseJson = json_decode($response->getBody());
$myToken = eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzUxMiJ9.eyJpYXQiOjE0NjQ5NzExMzQsImp0aSI6IjByR3FpOW15Rm1rRGo2TU9sMVhkK3dRU3p1V0pWejM1UEhiU2dTMmg5SEU9IiwiaXNzIjoiQXBwTmFtZSIsIm5iZiI6MTQ2NDk3MTE0NCwiZXhwIjoxNDY0OTczMTQ0LCJzdWIiOiJ0ZXN0QG1haWwuZGUifQ.yA4a_S6ILCeqENm00H712g9uF5g9eSz_BmnaMDdZ2r4p5e1q88g0T09IG2WKCi1oExoBfQ8VTmKeX6ZQv0RydQ;

$response = $client->request('GET', 'users', [
    'headers' => [
        'Authorization'      => 'Bearer '.$myToken
    ],
    'debug' => true
]);

}
`

and also with Postman, it is working. It's the same token which I receive from my REST-API.

When I debug the code & wait on line: $response = $client->request('GET', 'users'... the test is ok... seems to be something wrong with asynch requesting ?

Do you have any ideas what's wrong?

Exceptions in nested handlers are not reported

$p = new Promise();
$p->then(function () {
    $inner = new Promise();
    $inner->then(function () {
        throw new \RuntimeException('This exception should not be trapped');
    });
    $inner->resolve(true);
    $inner->wait();

    print_r(['state' => $inner->getState()]);

    return $inner;
});
$p->resolve(true);
$p->wait();

print_r(['state' => $inner->getState()]);

In this scenario, the RuntimeException should not be trapped. Both promises are correctly fulfilled but the exception is never seen.

Expected Result

  1. RuntimeException is thrown.

Current Result

  1. RuntimeException is never seen.

Add splat() function

I was just writing some code that would have been greatly improved by having a splat() function that works similarly to all().

The current code is:

Promise\all($promises)->then(function ($results) {
    list($first, $second) = $results;
    ...
});

But what I would rather write is:

Promise\splat($promises)->then(function ($first, $second) {
    ...
});

This would require PHP >= 5.6 for argument unpacking or the use of call_user_func_array.

Useless library?

I don't get it how is this useful if it doesn't work async? The whole point of promises is to execute code async... so like wtf?

Inject TaskQueue and remove global TaskQueue?

If we're brainstorming on 2.0 ideas... then what about removing the concept of a global dependency on a TaskQueue? In the current implementation, promises are added to a global TaskQueue. That queue can also be swapped out for other queues if needed (like React), but doing so might have implications on other aspects of an application that were relying on a different global TaskQueue's behavior. The bigger problem is that because there's a global TaskQueue, Guzzle can't be used in multiple threads.

A better approach would be to require that a promise is created with a reference to the TaskQueue so that chaining off of a promise would add the tasks to the right queue.

This kind of change would probably require another abstraction and a lot of work to ensure it is ergonomic. I'd also be interested to see if we could ensure that there is not a circular reference between a promise and a TaskQueue.

Confused about the order of the ->then callback resolution

I would expect the 3 ->then to come up in the 3rd place. Is this a bug or as per design? What is the logic behind it?

It looks like a bug because of chaining ->then()->then, since on the fair race, one then comes after the other but always on the promise object instead of in the return value of the last then...

$cheating = new GuzzleHttp\Promise\Promise(
    function () use(&$cheating){
        $cheating->resolve(null);
        echo "\n--- Finish line ---\n";
    }
);

$cheating->then(function() {
    echo "I'm the #1!\n";
})->then(function() {
    echo "I'm the #2...\n";
});

$cheating->then(function() {
    echo "Haha! And I'm the #3? Nop!\n";
});

$cheating->wait(false);

$fairRace = new GuzzleHttp\Promise\Promise(
    function () use(&$fairRace){
        $fairRace->resolve(null);
        echo "\n--- Finish line on Fair Race ---\n";
    }
);

$fairRace->then(function() {
    echo "I'm the #1!\n";
});
$fairRace->then(function() {
    echo "I'm the #2...\n";
});

$fairRace->then(function() {
    echo "I'm the #3 :(\n";
});

$fairRace->wait(false);

This is the output I see:


--- Finish line ---
I'm the #1!
Haha! And I'm the #3? Nop!
I'm the #2...

--- Finish line on Fair Race ---
I'm the #1!
I'm the #2...
I'm the #3 :(

My composer.lock says I'm using 1.2

            "version": "1.2.0",
            "source": {
                "type": "git",
                "url": "https://github.com/guzzle/promises.git",
                "reference": "c10d860e2a9595f8883527fa0021c7da9e65f579"

Changeable TaskQueue for better event loop integration

I would like to propose a change for better event loop integration.

The issues

Current way for event loop integration (using addPeriodicTimer or futureTick) leads to 100% CPU load, because it kills the whole idea. There is always something to do (run the promises queue), so the timeout before the next tick is always 0 or near it.

It also leads to an endless event loop, because, again, there is always something to do.

The proposed solution

Make the TaskQueue changeable in the code, to be able to set a proper implementation in case of an event loop. A simple event loop implementation could do $loop->nextTick([$queue, 'run']) inside the add() method, so only when it's needed.

I think it can be done with

  1. introducing TaskQueueInterface that will contain all methods from the current TaskQueue,
  2. adding TaskQueue::getInstance() and TaskQueue::setInstance(TaskQueueInterface $q) methods,
  3. changing calls to queue() inside the code to TaskQueue::getInstance().

This way doesn't require BC breaks.

What do you think about it?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.