Нет, это не из-за Windows, проблема в вашем коде, хотя она и не так очевидна как кажется, пришлось повозиться чтобы найти ответ.
Если вкратце то основная проблема - в использовании
sleep()
для эмуляции задержки. Ведь в отличие от того же
setTimeout()
в JavaScript который нативно использует event loop, а следовательно асинхронен,
sleep()
в PHP - это просто задержка т.е. блокирующая операция. Ваш код не мог продолжаться дальше пока не отработает
sleep()
, отсюда и последовательность выполнения которая по факту практически синхронная.
Для получения асинхронной задержки вам необходимо было использовать
LoopInterface::addTimer(), тогда код начинает работать как надо.
Немного изменённый вариант вашего кода, с форматированием и выводом задержки давал:
[3] 3 sec
[2] 5 sec
[1] 6 sec
причём и на Windows и на Linux.
Если же использовать вариант приведённый ниже - то результат становится ожидаемым, и на Windows и на Linux:
[1] 1 sec
[2] 2 sec
[3] 3 sec
Ниже приведён код, дающий правильный результат, я не стал сильно менять структуру, хотя её можно упростить.
server.php<?php
use React\EventLoop\Factory;
use React\Http\Response;
use React\Http\Server;
require_once __DIR__ . '/vendor/autoload.php';
require_once __DIR__ . '/async.php';
$loop = Factory::create();
$socket = new \React\Socket\Server($argv[1] ?? '0.0.0.0:0', $loop);
$server = new Server(static function () use ($loop) {
$test = new Async($loop);
return $test->run()->then(static function () use ($test) {
return new Response(
200,
['Content-Type' => 'text/plain'],
(string)$test
);
});
});
$server->listen($socket);
echo 'Listening on ' . str_replace('tcp:', 'http:', $socket->getAddress()) . PHP_EOL;
$loop->run();
async.php<?php
use React\EventLoop\LoopInterface;
use React\Promise\PromiseInterface;
class Async
{
protected $_stdout;
/**
* @var LoopInterface
*/
private $loop;
/**
* @param LoopInterface $loop
*/
public function __construct(LoopInterface $loop)
{
$this->loop = $loop;
}
public function __toString()
{
return $this->_stdout;
}
public function run(): PromiseInterface
{
$start = time();
$handler = function ($x) use ($start) {
$this->_stdout .= sprintf("[%d] %d sec\n", $x, time() - $start);
};
return React\Promise\all([
$this->asyncAction(3)->then($handler),
$this->asyncAction(2)->then($handler),
$this->asyncAction(1)->then($handler),
]);
}
protected function asyncAction(int $sleep = 0): PromiseInterface
{
return new React\Promise\Promise(function ($resolve, $reject) use ($sleep) {
$this->action($sleep, static function ($e, $result) use ($resolve, $reject) {
if ($e) {
$reject($e);
} else {
$resolve($result);
}
});
});
}
protected function action(int $sleep, callable $callback): void
{
$this->loop->addTimer($sleep, static function () use ($sleep, $callback) {
$callback(null, $sleep);
});
}
}