Guzzle Pool: ожидание запросов

можно ли заставить пул жрать ждать запросов?

прямо сейчас я могу добавлять запросы в пул динамически, но как только пул будет пуст, guzzle остановится (очевидно).

это проблема, когда я делаю 10 или около того страниц одновременно, потому что мой массив запросов будет пустым, пока полученные HTML-страницы не будут обработаны и добавлены новые ссылки.

Это мой генератор:

$generator = function () {
  while ($request = array_shift($this->requests)) {
    if (isset($request['page'])) {
      $key = 'page_' . $request['page'];
    } else {
      $key = 'listing_' . $request['listing'];
    }

    yield $key => new Request('GET', $request['url']);                                          
  }
  echo "Exiting...n";
  flush();
};

и мой бассейн:

$pool = new Pool($this->client, $generator(), [
  'concurrency' => function() {
    return max(1, min(count($this->requests), 2));
  },
  'fulfilled' => function ($response, $index) {
      // new requests may be added to the $this->requests array here
  }
  //...
]);

$promise = $pool->promise();
$promise->wait();

отредактированный код после ответа @Alexey Shockov:

$generator = function() use ($headers) {
  while ($request = array_shift($this->requests)) {
    echo 'Requesting ' . $request['id'] . ': ' . $request['url'] . "rn";

    $r = new Request('GET', $request['url'], $headers);

    yield 'id_' . $request['id'] => $this->client->sendAsync($r)->then(function($response, $index) {
      echo 'In promise fulfillment ' . $index . "rn";
    }, function($reason, $index) {
      echo 'in rejected: ' . $index . "rn";
    });
  }
};

$promise = GuzzleHttpPromiseeach_limit($generator(), 10, function() {
  echo 'fullfilled' . "rn";
  flush();
}, function($err) {
  echo 'rejected' . "rn";
  echo $err->getMessage();
  flush();
});
$promise->wait();

4 ответов


к сожалению, вы не можете сделать это с генератором, только с пользовательским итератором.

приготовил суть с полным примером, но основная идея - просто создать итератор, который изменит свое состояние в обоих направлениях (он может снова стать действительным после окончания).

пример с ArrayIterator в Псыш:

>>> $a = new ArrayIterator([1, 2])
=> ArrayIterator {#186
     +0: 1,
     +1: 2,
   }
>>> $a->current()
=> 1
>>> $a->next()
=> null
>>> $a->current()
=> 2
>>> $a->next()
=> null
>>> $a->valid()
=> false
>>> $a[] = 2
=> 2
>>> $a->valid()
=> true
>>> $a->current()
=> 2

имея в виду эту идею, мы можем передать такой динамический итератор жрать и оставить его делать работа:

// MapIterator mainly needed for readability.
$generator = new MapIterator(
    // Initial data. This object will be always passed as the second parameter to the callback below
    new \ArrayIterator(['http://google.com']),
    function ($request, $array) use ($httpClient, $next) {
        return $httpClient->requestAsync('GET', $request)
            ->then(function (Response $response) use ($request, $array, $next) {
                // The status code for example.
                echo $request . ': ' . $response->getStatusCode() . PHP_EOL;
                // New requests.
                $array->append($next->shift());
                $array->append($next->shift());
            });
    }
);
// The "magic".
$generator = new ExpectingIterator($generator);
// And the concurrent runner.
$promise = \GuzzleHttp\Promise\each_limit($generator, 5);
$promise->wait();

как я уже говорил, полный пример в суть С MapIterator и ExpectingIterator.


из вопроса кажется, что вы можете переместить обратный вызов агрегации непосредственно в запрос. В этом случае пул всегда будет ждать вашего кода обработки, поэтому вы сможете добавлять новые запросы в любой момент.

генератор может вернуть либо запрос, либо обещание, и обещания могут быть объединены по-разному.

$generator = function () {
    while ($request = array_shift($this->requests)) {
        if (isset($request['page'])) {
            $key = 'page_' . $request['page'];
        } else {
            $key = 'listing_' . $request['listing'];
        }

        yield $this->client->sendAsync('GET', $request['url'])
            ->then(function (Response $response) use ($key) {
            /*
             * The fullfillment callback is now connected to the query, so the 
             * pool will wait for it.
             * 
             * $key is also available, because it's just a closure, so 
             * no $index needed as an argument.
             */
        });
    }
    echo "Exiting...\n";
    flush();
};

$promise = \GuzzleHttp\Promise\each_limit($generator(), [
    'concurrency' => function () {
        return max(1, min(count($this->requests), 2));
    },
    //...
]);

$promise->wait();

Как я уже говорил, полный пример находится в gist, с MapIterator и ExpectingIterator

итераторы не снова станьте действительными на php

С другой стороны, все это работает на более ранних версиях php, если вы используете метод ->append на итераторе вместо [] push.


ответ-да, можно. Вам просто нужно больше генераторов. И разделить логику разбора запросов и очереди на асинхронный дизайн. Вместо использования массива для вашего бассейна будет выдавать и ждать он должен быть сам генератор, который дает новые запросы от вашего первоначального списка запросов и добавил от разобранных ответов, пока все запросы отправляются анализируется и в результате запроса отправляются и анализируются (повторяющихся) или условие остановки встречаются.