reactphp / promise

Promises/A implementation for PHP.
https://reactphp.org/promise/
MIT License
2.38k stars 146 forks source link

Blocking a promise from running untill another promise has completed. #191

Closed JABirchall closed 2 years ago

JABirchall commented 3 years ago

Im working on a realtime data platform. We have run into an issue where sometimes data wont exist yet that a promise needs to complete. We cant reject the promise, it must run, and is already halfway completed.

We need the data to be loaded from an external API, what we are trying to do is have the current promise wait until the data is loaded from a separate promise.

How would this be possible?

We were thinking of a while loop that could move forward the eventloop until the separate promise had completed and loaded the data we need to continue. But upon checking the source this is not possible, but would solve our problem.

JABirchall commented 3 years ago

I cant share specific code because its not my property to share. But here is a cut down nature of what we are attempting.

    public function __get($name)
    {
        if ($name === "something") {
            $repository = RepoManager::get('repo');
            $repository->setSomething($this->something->id);
            $data = $repository->getById($this->id);
            if($data === null) {
                $repository->api->get($this->id); // Creates the promise to fetch the data from the API and store in the repository
                $loop = DependencyManager::get('Loop');

                while($data = $repository->getById($this->id) === null) { // Idea to run the loop until our promise has completed and data exists?
                    $loop->moveForward();  // run the loop once until it is completed our required promise?
                }
            }

            return $data;
        }
    }

We need a way to run $repository->api->get($this->id) to completion inline our current promise so we can use the data.

WyriHaximus commented 3 years ago

What you can do is chain the promises:

$this->db->operationA()->then(fn () => $this-db->operationB());

This ensures your second operation isn't started before the first has completed.

JABirchall commented 3 years ago

What you can do is chain the promises:

$this->db->operationA()->then(fn () => $this-db->operationB());

This ensures your second operation isn't started before the first has completed.

How would that work down the call tree? Because this is being called as a normal __get. Expecting the $data to be a value not a promise.

$object->something with the expectation ->something will be the data relative to the object. The $object is getting passed down a promise chain and ->something is being accessed conditionally Sometimes it might get called, sometimes it might not, and sometimes we might already have the data loaded and other times not.

The database is a Redis server which we don't wrap in promises, we are trying to keep promises down to a minimal needed use case, for example the API calls is a promise. Redis data isn't as it is fast enough for our use.

HLeithner commented 3 years ago

To my current understanding of reactphp what you like to achieve is against the principals of reactPHP and the promise approach.

If you want to have a function blocking and always return a value then you can't run async methods within the function. You have to return a promise.

You can't tell reactPHP "please do the next promise in queue and come back after it is solved and ask me again".

ymmv

JABirchall commented 3 years ago

To my current understanding of reactphp what you like to achieve is against the principals of reactPHP and the promise approach.

If you want to have a function blocking and always return a value then you can't run async methods within the function. You have to return a promise.

You can't tell reactPHP "please do the next promise in queue and come back after it is solved and ask me again".

ymmv

Thanks, that's a shame, because that's what we exactly need. I get what you say, but its also not blocking per say, as I want to direct execution to another promise chain until its resolved, same to how JS await works.

I tried this library, https://github.com/adrianmihaila/reactphp-promise-wait but that don't work with newer versions of reactphp.

WyriHaximus commented 3 years ago

Thanks, that's a shame, because that's what we exactly need. I get what you say, but its also not blocking per say, as I want to direct execution to another promise chain until its resolved, same to how JS await works.

Then you're going to love fibers coming in PHP 8.1. With those most of our public API is likely to change. Until than you can use https://github.com/recoilphp/recoil

I tried this library, https://github.com/adrianmihaila/reactphp-promise-wait but that don't work with newer versions of reactphp.

Would suggest using https://github.com/clue/reactphp-block instead. But keep in mind it's a hack to keep starting and stopping the loop.

JABirchall commented 3 years ago

Would suggest using https://github.com/clue/reactphp-block instead. But keep in mind it's a hack to keep starting and stopping the loop.

Thank you, this works perfectly.

I see what you mean by starting/stopping the loop. I had to wrap my $loop->run() in a while loop to keep the application alive.

Then you're going to love fibers coming in PHP 8.1. With those most of our public API is likely to change.

So will this come with a an await feature?

Our current targeted version is 7.4 with PHP8 support, I doubt we would move over to only PHP 8.1 support so soon, we want to keep some BC

WyriHaximus commented 3 years ago

Would suggest using clue/reactphp-block instead. But keep in mind it's a hack to keep starting and stopping the loop.

Thank you, this works perfectly.

I see what you mean by starting/stopping the loop. I had to wrap my $loop->run() in a while loop to keep the application alive.

Yes, that is the price you're paying for doing it this way. Which is something we're aiming to solve with fibers in 8.1.

Then you're going to love fibers coming in PHP 8.1. With those most of our public API is likely to change.

So will this come with a an await feature?

Our current targeted version is 7.4 with PHP8 support, I doubt we would move over to only PHP 8.1 support so soon, we want to keep some BC

In a nutshell yes. Behind the scenes, it's a lot more complicated but it will let you write that example of yours a bit up like this, without any promises insight:

    public function __get($name)
    {
        if ($name === "something") {
            $repository = RepoManager::get('repo');
            $repository->setSomething($this->something->id);
            return $repository->getById($this->id);
        }
    }

We might be able to provide a similar API on an earlier PHP version but we aren't sure yet if that is possible and feasible. But yeah I get your concern, for us it would main bumping the minimum PHP version requirement of this project from 5.3 (yes it all still works on that version (I know it's crazy don't ask)) to 8.1.

JABirchall commented 3 years ago

I just read over the fibres RFC, and yea looks pretty much onpoint with what we currently needed. Nice to see PHP get some engine level async functionality.

SimonFrings commented 2 years ago

Hey @JABirchall, I don't know if I'm too late to the party but PHP 8.1 is out and fibers are a thing now!

With that said you can also check out the new react/async package that has been created in addition to PHP's new release. It's still a development version but we're not far from a stable version. ^^

clue commented 2 years ago

I believe this has been answered, so I'm closing this for now. Please come back with more details if this problem persists and we can always reopen this :+1: