ReactPHP React/PromiseでRetry処理

単純に書いたら、sleep して再度実行とかやるかもしれないけど、イベントループではそんなことしたら全部止まっちゃうので、3秒後に実行するのにタイマー使って実装してみた。

実装Aと実装Bはやっていることは変わらないんだけど、実装Aは素直にリトライ処理を実装した感じで、実装Bはresolverとして利用することを前提としてクロージャでラップして返しているだけ、実装Aの場合、一度自分でラップしなきゃならないのが気になって、実装Bを作ってみたけど...。どっちが良いだろう...。

## 実装Ａ

```php
/**
 * @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();
}
```

### 実装Ａサンプル

```php
$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();
```

## 実装Ｂ

```php
/**
 * @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();
    };
}
```

### 実装Ｂサンプル

```php
$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

```json
{
    "require": {
        "react/react": "0.4.*",
        "react/promise": "~2.1",
        "guzzlehttp/guzzle": "~5.1",
        "wyrihaximus/react-guzzle-ring": "dev-master"
    }
}
```