単純に書いたら、sleep して再度実行とかやるかもしれないけど、イベントループではそんなことしたら全部止まっちゃうので、3秒後に実行するのにタイマー使って実装してみた。
実装Aと実装Bはやっていることは変わらないんだけど、実装Aは素直にリトライ処理を実装した感じで、実装Bはresolverとして利用することを前提としてクロージャでラップして返しているだけ、実装Aの場合、一度自分でラップしなきゃならないのが気になって、実装Bを作ってみたけど...。どっちが良いだろう...。
実装A
/**
* @param LoopInterface $loop
* @param callable $callback リトライする処理
* @param int $interval リトライ間隔
* @param int $maximum 最大リトライ回数
* @param int $retry リトライカウンタ
* @param Deferred $deferred
*
* @return PromiseInterface
*/
function retry($loop, $callback, $interval = 3, $maximum = 3, $retry = 0, $deferred = null)
{
$deferred = $deferred ?: new \React\Promise\Deferred();
/** @var PromiseInterface $promise */
$promise = $callback();
$promise->then(
function ($response) use ($deferred) {
$deferred->resolve($response);
},
function (\Exception $e = null)
use ($loop, $callback, $interval, $maximum, $retry, $deferred) {
if ($e !== null)
echo sprintf('%s:%s in %s at %d.', get_class($e), $e->getMessage(), __FILE__, __LINE__).PHP_EOL;
if ($retry++ >= $maximum)
return $deferred->reject($e);
// タイマーで $interval 後にリトライする
$loop->addTimer($interval,
function ($timer)
use ($loop, $callback, $interval, $maximum, $retry, $deferred) {
retry($loop, $callback, $interval, $maximum, $retry, $deferred);
}
);
}
);
return $deferred->promise();
}
実装Aサンプル
$loop = React\EventLoop\Factory::create();
$client = new \GuzzleHttp\Client([
'handler' => new \WyriHaximus\React\RingPHP\HttpClientAdapter($loop)
]);
$deferred = new React\Promise\Deferred();
$deferred->promise()
->then(function ($url) use ($client, $loop) {
// リトライする
return retry($loop, function () use ($url, $client, $loop) {
return $client->get($url, ['future' => true])->promise();
});
})
->then(function ($response) {
echo $response->getStatusCode();
});
$deferred->resolve('http://127.0.0.1/');
$loop->run();
実装B
/**
* @param LoopInterface $loop
* @param callable $callback
* @param int $interval
* @param int $maximum
* @param int $retry
* @param Deferred $deferred
*
* @return callable
*/
function retry_wrapper($loop, $callback, $interval = 3, $maximum = 3, $retry = 0, $deferred = null)
{
return function ($response)
use ($loop, $callback, $interval, $maximum, $retry, $deferred) {
$deferred = $deferred ?: new \React\Promise\Deferred();
$promise = $callback($response);
$promise->then(
function ($response) use ($deferred) {
$deferred->resolve($response);
},
function (\Exception $e = null)
use ($loop, $callback, $response, $interval, $maximum, $retry, $deferred) {
if ($e !== null)
echo sprintf('%s:%s in %s at %d.', get_class($e), $e->getMessage(), __FILE__, __LINE__).PHP_EOL;
if ($retry++ >= $maximum)
return $deferred->reject($e);
$loop->addTimer($interval,
function ($timer)
use ($loop, $callback, $response, $interval, $maximum, $retry, $deferred) {
$wrapped = retry_wrapper(
$loop, $callback, $interval, $maximum, $retry, $deferred
);
$wrapped($response);
}
);
}
);
return $deferred->promise();
};
}
実装Bサンプル
$loop = React\EventLoop\Factory::create();
$client = new \GuzzleHttp\Client([
'handler' => new \WyriHaximus\React\RingPHP\HttpClientAdapter($loop)
]);
$deferred = new React\Promise\Deferred();
$deferred->promise()
->then(retry_wrapper($loop, function ($url) use ($client, $loop) {
return $client->get($url, ['future' => true])->promise();
}))->then(function ($response) {
echo $response->getStatusCode();
});
$deferred->resolve('http://127.0.0.1/');
$loop->run();
composer.json
{
"require": {
"react/react": "0.4.*",
"react/promise": "~2.1",
"guzzlehttp/guzzle": "~5.1",
"wyrihaximus/react-guzzle-ring": "dev-master"
}
}