Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Synchrony of promises #43

Closed
khelle opened this issue Sep 13, 2015 · 7 comments
Closed

Synchrony of promises #43

khelle opened this issue Sep 13, 2015 · 7 comments
Labels

Comments

@khelle
Copy link

khelle commented Sep 13, 2015

Hello everyone,
to begin with I am big fan of React project in general, its very promising, congratulation guys! I am sorry for writing a question here, but since this project is hard to find in google at all, waiting for answer in stackoverflow or similar service would take me forever.
I recently discovered promises and was very glad that someone implemented them in PHP since this is a feature I need in my recent project. However I can't understand one thing about react implementation of them and I hope you can help me with that. I read some older issues and have seen that you guys don't want to incorporate ReactLoop into promises, but I can't understand why. Since PHP by default is synchronous and one-threaded, using promises without loop results in synchronous execution of all callbacks in quite atomic-way which negates purpose of promises and is against Promise/A+ specification that says calling callbacks should be done asynchronously. Is there a technical problem you have difficulty with right know for proper implementation or did I understand the idea wrongly?

@jsor
Copy link
Member

jsor commented Sep 14, 2015

Hi,

thanks for the question, i'll try to clarify that.

Promises itself don't make your code execute asynchronously. Promises are only placeholders for the results which are produced by asynchronous operations.
The reason we don't want to couple React/Promise to the React event loop is simply, that you can use React/Promise with other event loop implementation or other libraries which handle non-blocking I/O (for example, Guzzle 5 uses React/Promise but does its asynchronous operations with the curl_multi_* functions).

Consider the following example:

$loop = React\EventLoop\Factory::create();

$asyncPromise = new React\Promise\Promise(function($resolve) use ($loop) {
    $loop->nextTick(function() use($resolve) {
        $resolve('Async Promise');
    });
});

$syncPromise = new React\Promise\Promise(function($resolve) {
    $resolve('Sync Promise');
});

$asyncPromise->then(function($value) {
    echo $value.PHP_EOL;
});

$syncPromise->then(function($value) {
    echo $value.PHP_EOL;
});

$loop->run();

This outputs:

Sync Promise
Async Promise

The resolution value of $asyncPromise is echoed after the one of $syncPromise because its result is produced asynchronously by using the event loop.

What Promises/A+ says is:

Here “platform code” means engine, environment, and promise implementation code. In practice, this requirement ensures that onFulfilled and onRejected execute asynchronously, after the event loop turn in which then is called, and with a fresh stack. This can be implemented with either a “macro-task” mechanism such as setTimeout or setImmediate, or with a “micro-task” mechanism such as MutationObserver or process.nextTick. Since the promise implementation is considered platform code, it may itself contain a task-scheduling queue or “trampoline” in which the handlers are called.

What this means is, that the previous example always outputs

Async Promise
Sync Promise

even for the $syncPromise which is resolved synchronously. In JavaScript this is relatively easy because the platform itself offers the event loop which can be used by the promise implementation.

And this is where React/Promise differs from Promises/A+ simply because in PHP, non-blocking/asynchronous execution requires a userland event loop implementation.

@khelle
Copy link
Author

khelle commented Sep 15, 2015

Thank you for your detailed answer. It is nice to see there are still people open to discussion on github rather than sending anyone to stackoverflow. Now I understand why you didn't use ReactLoop, however I still see no way of adding asynchronous userland code without modifying Promise code. Consider this simple piece of code, which executes transaction consisting of A, B, C tasks as promise.

$deferred = new Deferred();
$deferred
    ->promise()
    ->then(function($input) use($loop) {
        $loop->futureTick(A($input));
    })
    ->then(function($input) use($loop) {
        $loop->futureTick(B($input));
    })
    ->then(function($input) use($loop) {
        $loop->futureTick(C($input));
    })
;

This is the only way of trying to add asynchrony without modyfing Promise code, however it now breaks abstraction, because there is no way of catching returned values and decide whether to execute success or failure callback of next promise. It could be done if current implementation supported pause/resume calls to the PromiseChain like:

$deferred = new Deferred();
$deferred
    ->promise()
    ->then(function($input) use($loop) {
        $loop->futureTick(function() {
            $deferred->continue(A($input)));
        })
        $deferred->pause();
    })
    ->then(function($input) use($loop) {
        $loop->futureTick(function() {
            $deferred->continue(A($input)));
        })
        $deferred->pause();
    })
    ->then(function($input) use($loop) {
        $loop->futureTick(function() {
            $deferred->continue(A($input)));
        })
        $deferred->pause();
    })
;

As you see now each asynchronous callback pauses atomic resultion of next promise and also continues ("resolves" or "rejects") execution of the chain when return value is ready. This is quite similar to http://taskjs.org/ approach.

Can you give me a hints if this is really missing right now, and then if you like proposal, or maybe it is currently possible to implement this async chain with resolve/cancel toggling?

@jsor
Copy link
Member

jsor commented Sep 15, 2015

Promises are not meant to make your code async/non-blocking. Promises are just placeholders for results returned by function which work asynchronously. Functions which produce their result asynchronously cannot return an immediate value, as such they return a promise which act as a placeholder for the return value.

function doSomethingAsync()
{
    $deferred = new Deferred();

    // Do some async operations. We cannot return something immediately because
    // the result will be available later, in 1 second or even in 1 hour...
    // produceAsyncResult calls $deferred->resolve($result); once the result is available.
    produceAsyncResult($deferred);

    return $deferred->promise();
}

$promise = doSomethingAsync();
$promise->then(function($result) {
   // The callback will be called once the result is available
});

To use your example, A(), B() and C() must operate async and return promises. So, the code would look:

A($input)
    ->then(function($resultFromA) {
        return B($resultFromA);
    })
    ->then(function($resultFromB) {
        return C($resultFromB);
    })
    ->then(function($resultFromC) {
    })
;

@khelle
Copy link
Author

khelle commented Oct 9, 2015

Thank you, now I understand it better. The problem I had was that before looking into code of React\Promise and analyzing JS libraries for it I was not aware that returning Promise from then() method callbacks invokes adapting its state. I think it would be beneficial to add example of this behaviour to main Readme.

@skolodyazhnyy
Copy link

@jsor Sorry, but I'm not really getting your examples,

function doSomethingAsync()
{
    $deferred = new Deferred();

    // If produceAsyncResult will, let's say, send an API call we will be blocked here,
    // until moment we receive response
    // If produceAsyncResult will send a request but won't wait for reply $deferred will
    // never be resolved because PHP will return control to this function
    produceAsyncResult($deferred);

    // Promise at this moment will be already resolved because we was blocked above 
    return $deferred->promise();
}

May be something like this make more sense for PHP?

function doSomethingAsync()
{
    $deferred = new Deferred();

    // Send request here
    produceAsyncResult($deferred);

    return $deferred->promise();
}

$promise = doSomethingAsync();
$promise->then(function($result) {
   // The callback will be called once the result is available
});

waitForAsyncResultToArrive(); // wait for reply and then resolve/reject promise

@jsor
Copy link
Member

jsor commented Nov 24, 2015

If produceAsyncResult blocks, then yes, the promise will be resolved synchronously. If you are in an asynchronous environment, every I/O must be non-blocking. For HTTP request, you must use something like react/http-client.

@skolodyazhnyy
Copy link

Ok, got it. react/http-client require you to call $loop->run(); which is practically same as waitForAsyncResultToArrive() in my example.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

4 participants