-
-
Notifications
You must be signed in to change notification settings - Fork 117
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
Requesting implementation of done() #44
Comments
@socketman2016 Do you have a use case for that? Doesn't As far as I can see, /**
* @throws Exception
*/
function done($promise, $onFulfilled = null, $onRejected = null) {
$promise->then($onFulfilled, $onRejected)->wait();
}
// instead of $promise->done():
done($promise); Or, depending on your needs: /**
* @throws Exception
* @return Promise
*/
function throwRejected($promise) {
return (new Promise())->resolve($promise->wait());
}
$promise = throwRejected($promise); |
@schnittstabil The functions you propose are both blocking. |
@socketman2016 wrote at reactphp/promise#66 (comment):
@socketman2016 Details would be nice. Looks like a design issue IMO. If you already have error handlers, why don't you call them? I see no need to take a detour via php error mechanisms. function existingErrorHandler($error) {…}
function promiseErrorHandler($error) {
// do what ever you want, e.g.
existingErrorHandler($error);
// and/or
trigger_error($error->message, …);
// and/or
die();
}
$promise->then(null, promiseErrorHandler); |
Yes , I use event loop in my application , it is a PHP script that runs for months and it is not possible for me to use So
For many reason I cannot do that , once is my script must not never The best solution for my problem is |
@socketman2016 I don't feel capable enough of implementing I've used a similar approach the other day: function error_handler($error) {
// do logging stuff, etc.
}
function exception_error_handler($errno, $errstr, $errfile, $errline ) {
errorHandler(new ErrorException($errstr, 0, $errno, $errfile, $errline));
}
set_error_handler("exception_error_handler");
// Therefore, it's easy to do:
$promise->then(null, error_handler); I think your most important concern is performance, but calling
Especially 2. and 3. seem inefficient and unnecessary to me.
To be honest, I don't understand why it is not possible. Anyway, I believe the approach above isn't affected by this.
This might be true, but function onFulfilled() {
if (…) throw …;
}
$promise->then(…)->done(onFulfilled)->then(function () {
// ok, no error, do the next step
}); |
Dear @schnittstabil, I dont want to tell all reasons for verbosity , but one of the most important is : Assume we want to run a public function onMessage($request){
$this->process($request);
$this->sendResponse();
}
public function process($request){
//Part 1
//Part 2
//Part 3
//Part 4
} Assume Parts 1-4 is running in an asynchronous mode .Remember that each part can be either a promise or not . the problem is some of asynchronous codes are not promise . We have 2 requests , A and B . Assume the following scenario
If there is no error in processing all things work good , But when we have an error in public function onMessage($request){
try{
$this->process($request);
$this->sendResponse();
}catch(\Exception $e){
//Send error and log
}
} So when About the point that you have mentioned i. e. " I would be really thankful and glad if you consider the implementation of done in your planning and agenda. I impatiently wait for merging and releasing the new version. |
@socketman2016 Thanks for providing some insights. Maybe the following doesn't meet your requirements, I don't know how your non-promise asynchronous code looks like. Personally, I would switch to a pure promise-based approach: public function process($request){
// we start with a promise, this can be done in many ways,
// this one is rather opinionated, but sometimes quite helpful
return (new Promise())->resolve(null)
->then(function ($result) {
// Of course, $result is null
// lets do a sync task
//Part 1
// if an exception is thrown within then-closures, the promise gets rejected
$result = …;
// to resolve the promise just return the result
return $result;
})->then(function ($result) {
// $result is the return value of Part 1
// (we don't get here, if Part 1 throws an error)
//Part 2
// lets assume this is a promise task
$promise = doSomeAsyncWork($result);
// we don't need to ->wait() or ->done(), just return the promise
return $promise;
})->then(function ($result) {
// $result is NOT a promise, it's the resolved value of the Part 2 $promise
// (we don't get here, if Part 2 was rejected)
// lets do a non-promise async task, but first create a new promise:
$promise = new Promise();
//Part 3
//…
// somewhere, deep nested you get a result:
$result = …;
// and resolve your promise
$promise->resolve($result);
//…
return $promise;
});
}
public function onMessage($request){
$this->process($request)
->then(function ($result) {
// $result is the value of Part 3
$this->sendResponse();
}, function ($error) {
// $error can be:
// a thrown Exception, from e.g. Part 1
// or the value of a rejected promise, e.g. Part 2 or Part 3
});
} |
Pure promise-based approach has some drawbacks , in my application it is very hard coded. As I told before , For simplicity I just simulate my problem , An in real project I have many other complicated challenges , So I dont want to migrate to pure promise-based approach .One of the most important challenge that prevent me to migrate to pure promise-based approach is joining asynchronous parts. I think in pure promise-based approach it will be spaghetti code Consider the following scenario , To response request Do you still think edit: Also it may be |
Promises want to make working with asynchronous operations much more pleasant. Joining asynchronous parts is quite easy: // forking
$promises = [];
$promises[] = createPromiseA();
$promises[] = createPromiseB();
// joining
Promise\all($promises)->then(function ($results) {
list($valueA, $valueB) = $results;
...
}); It's a bit unfortunate that The above also gives a hint how modular code is written with Promises: // instead of
$resultA = null;
createPromiseA()
->then(function ($valueA) use (&$resultA) {
$resultA = $valueA;
$resultB = 42;
return $resultB;
})
->then(function ($resultB) use (&$resultA) {
// now we have access to $resultA and $resultB
});
// we do
createPromiseA()
->then(function ($resultA) {
$resultB = 42;
return [
'a' => $resultA,
'b' => $resultB,
];
})
->then(function ($results) use (&$resultA) {
// now we have access to $results['a'] and $results['b']
});
I don't want to proselytize you 😉 I think
Yeah, sometimes things get really complicated. On cheap trick to keep the order of results in the joined Promise: // forking
$promises = [];
$promises[] = ($usePart1) ? createPromisePart1() : (new Promise())->resolve();
$promises[] = createPromisePart2();
// maybe this works too, I can't test it atm:
$promises[] = ($usePart1) ? createPromisePart1() : null;
// joining
Promise\all($promises)->then(…)
I would suggest a rather functional style, maybe you want to do it with more OO: function part2_2_2($args) {
return Promise\all([
createPromise(),
($usePart2) ? part2($args) : (new Promise())->resolve()
]);
}
function part2($args) {
return Promise\all([
createPromise(),
($usePart2_2_2) ? part2_2_2($args) : (new Promise())->resolve()
])->then(function ($results) {
// maybe you need to merge the results, e.g.
return $results[0] + $results[1];
});
}
// or if the condition is not known in advance
function part2($args) {
return createSomePromise()
->then(function ($result) {
if ($usePart2_2_2) {
return part2_2_2(…);
}
// return null or whatever fits best
});
} |
I think See reactphp/promise#66 again , It it nice that I am agree with you that As you know My approach is based on All Consider following code createPromiseA()
->then(function ($resultA) {
$resultB = 42;
return [
'a' => $resultA,
'b' => $resultB,
];
})
->then(function ($results){
// now we have access to $results['a'] and $results['b'] but here we dont need both
$resultC = 43;
return [
'a' => $resultA,
'b' => $resultB,
'c' => $resultC,
];
})->then(function ($results){
// now we have access to $results['a'] and $results['b'] and $results['c'] but here we only need $results['b']
$resultD = 43;
return [
'a' => $resultA,
'b' => $resultB,
'c' => $resultC,
'd' => $resultD,
];
})->then(function ($results){
// now we have access to $results['a'] to $results['d'] here we needs all results
});; This is very hard coded always we must do a boring job , but in my approach , All thing is an isolated @mtdowling , @schnittstabil , @joshdifabio and any other possible implementors , Please implement |
C'mon! Nobody would do it that way. The promise based approach forces one to follow some good practices, e.g. Exceptions are handle the same way as in sync code: they bubble up – at least if you don't use class Results {
// your domain specific class, e.g. Psr\Http\Message\ResponseInterface
}
// some tasks
function runTaskA($results) {
$results->a = 41;
return $results;
}
function runTaskB($results) {
$results->b = 42;
return $results;
}
function runTaskC($results) {
$results->c = 43;
return $results;
}
function runTaskD($results) {
$results->d = 44;
return $results;
}
// sync version
try {
$result = new Results();
$result = runTaskA($result);
$result = runTaskB($result);
$result = runTaskC($result);
$result = runTaskC($result);
// now we have access to $result->a to $result->d
} catch (Exception $err) {
// error handling
}
// promise version
createPromise(new Results())
->then(runTaskA)
->then(runTaskB)
->then(runTaskC)
->then(runTaskD)
->then(function ($result){
// now we have access to $result->a to $result->d
})
->then(null, function ($err){
// error handling
}); You have been warned, I don't want to bother you anymore 😉 – back to your problem. You didn't mentioned what kind of event loop you use, but maybe you are using React\EventLoop. In that case you can throw the exception at the next tick: First we need a dump helper classuse React\EventLoop\LoopInterface;
class ThrowAtNextTick
{
protected $loop;
public function __construct(LoopInterface $loop)
{
$this->loop = $loop;
}
protected function fixStacktrace($error)
{
if ($error instanceof Throwable || $error instanceof Exception) {
return $error;
}
try {
throw new Exception($error);
} catch (Throwable $err) {
return $err;
} catch (Exception $err) {
return $err;
}
}
public function __invoke($reason)
{
$reason = $this->fixStacktrace($reason);
$this->loop->nextTick(function () use ($reason) {
throw $reason;
});
}
} Then the nasty hackinguse GuzzleHttp\Client;
use GuzzleHttp\HandlerStack;
use WyriHaximus\React\GuzzlePsr7\HttpClientAdapter;
// bootstrapping
$loop = React\EventLoop\Factory::create();
$throwAtNextTick = new ThrowAtNextTick($loop);
$client = new Client([
'handler' => HandlerStack::create(new HttpClientAdapter($loop)),
]);
// down the rabbit hole
$loop->nextTick(function () use ($client, $throwAtNextTick) {
$promise = $client->getAsync('http://example.org')->then(function () {
throw new Exception(42);
});
// instead of $promise->done():
$promise->then(null, $throwAtNextTick);
});
// run the event loop
$loop->run(); |
Too awesome answer !! But I cannot understand why in try {
throw new Exception($error);
} catch (Throwable $err) {
return $err;
} catch (Exception $err) {
return $err;
} Is the try {
throw new Exception($error);
} catch (Exception $err) {
return $err;
} Or may be just return new Exception($error); Is right? or not? I want to know what you think about // promise version
createPromise(new Results())
->then(runTaskA)
->then(runTaskB)
->then(runTaskC)
->then(runTaskD)
->then(function ($result){
// now we have access to $result->a to $result->d
})
->then(null, function ($err){
// error handling
});
createPromise(new Results())
->then(runTaskA)
->then(runTaskOveridedB)
->then(runTaskC)
->then(runTaskD)
->then(function ($result){
// now we have access to $result->a to $result->d
})
->then(null, function ($err){
// error handling
}); I think we have to repeat all steps A-D (Copy and Past) to How is possible have a concept like |
In some edge cases new Exception(new stdClass);
// PHP 5.6 triggers a fatal Error:
// PHP Fatal error: Wrong parameters for Exception([string $exception …])
// PHP7 throws a Throwable:
// PHP Fatal error: Uncaught Error: Wrong parameters for Exception([string $message …]) Btw, if you are using use function GuzzleHttp\Promise\queue;
$loop->addPeriodicTimer(0, [queue(), 'run']); |
Using inheritance just for the sake of code reuse is almost always bad practice, it usually violates the Liskov substitution principle (LSP). That was one of the main reasons why However, what I can't recommend is mixing it with pure imperative style as well (global variables, global error handlers etc.): One possible option comes from Node.js: They extensively use
fs.readFile('/etc/passwd', function (err, data) {
if (err) {
console.error(err);
return;
}
console.log(data);
});
Personally, I wouldn't extend the Promise class, if that is what you have in mind. I would use Promises only as return value, e.g.: class Worker {
public function sum($val1, $val2) {
$promise = new Promise();
$promise->resolve($val1 + $val2);
return $promise;
}
public function mult($val1, $val2) {
$promise = new Promise();
$promise->resolve($val1 * $val2);
return $promise;
}
public function __invoke($val1, $val2) {
return $this->mult($val1, $val2)->then(function ($result) {
return $this->sum($result, 42);
});
}
}
$worker = new Worker();
$worker(1, 2)->then(…); Furthermore, don't worry about mixing callback and promise style. The nodejs convention allows us to convert Workers back and forth: $callbackWorker = function ($val1, $val2, callable $cb) {
// return some value
$cb(null, $val1 + $val2);
// or return null
$cb();
// or *throw* an error
$cb(new Exception());
};
function promisify(callable $cbWorker) {
return function () use ($cbWorker) {
$promise = new Promise();
$args = func_get_args();
// add callback:
$args[] = function ($err = null, $value = null) use ($promise) {
if ($err) {
$promise->reject($err);
} else {
$promise->resolve($value);
}
};
call_user_func_array($cbWorker, $args);
return $promise;
};
}
$promiseWorker = promisify($callbackWorker);
$promiseWorker(1,2)->then(function ($value) {
echo $value;
}); Or the other way around: $promiseWorker = function ($val1, $val2) {
$promise = new Promise();
$promise->resolve($val1 + $val2);
return $promise;
};
function callbackify($pWorker) {
return function () use ($pWorker) {
$args = func_get_args();
$cb = array_pop($args);
$promise = call_user_func_array($pWorker, $args);
$promise->then(function ($value) use ($cb) {
$cb(null, $value);
}, function ($reason) use ($cb) {
$cb($reason);
});
};
}
$callbackWorker = callbackify($promiseWorker);
$callbackWorker(42, 43, function ($err = null, $value = null) {
if ($err) {
echo 'ERROR: '.$err;
} else {
echo $value;
}
}); |
Please thumbs up this post if you think "implementation of |
I don't think |
I have the same situation. And two different promises (React & Guzzle) are using at the same time makes things messed up. Any suggestions? |
See reactphp/promise#66 please
I need
guzzlehttp/promises
implementsdone()
https://github.com/reactphp/promise#done-vs-then
The text was updated successfully, but these errors were encountered: