Метод использует одно соединение, созданое XMLHttpRequest, для запуска скрипта и получения статуса его исполнения: скрипт выполнил часть - послал байт, ещё выполнил - снова послал байт. Браузер, по количеству полученный байт, вычисляет процент исполнения или уменьшает счётчик. Обычно, нормальные программисты используют следующую модель: запускают скрипт, которому говорят - не обращай внимания если от тебя отсоединятся. Скрипт работает и пишет в файл статистику исполнения. В браузере скрипт по таймауту обновляет страничку (делает джэйкваери к серверу) и получает статистику исполнения. Ненормальные программисты выбрасывают прокладки из своего кода и работают на прямую. Тот скрипт который был запущен будет передавать стату текущего исполнения в то же соединение которое его запустило. А браузер получать инфу об исполнении в тот момент когда она была отправлена. xak первый: Для того что бы посылать из скрипта в браузер по байту, нужно отключить буфер вывода php скрипту, из скрипта сделать это не возможно. По этому добавим конфиг виртуальному хосту апача. Code: <Directory /var/www/html/progress> <IfModule mod_php7.c> php_admin_value output_buffering "Off" </IfModule> </Directory> В /var/www/localhost/progress нужно положить следующие файлы: index.html Code: <html> <script> var job = { prepend: "job" ,id: Math.round(Math.random()*100000000).toString(10) ,cd: null }; var span = null; function main(event) { span = document.getElementById("stats_span"); var params = "job_prepend="+job.prepend+"&job_id="+job.id; var req = new XMLHttpRequest(); req.addEventListener("progress", reqListener); req.open("GET", "worker.php?"+params); req.send(); } function reqListener(event) { if (event.loaded == 1) { var re = new RegExp(job.prepend+job.id+"\=([0-9]+)"); var mat = re.exec(document.cookie); if (typeof(mat[1]) == "string") job.cd = parseInt(mat[1], 10); } if (job.cd !== null) { job.cd -= 1; span.innerText = job.cd; } } window.addEventListener('load', main); </script> <body> <span id="stats_span">-</span> </body> </html> worker.php Code: <?php if (!(@ctype_alpha($_GET['job_prepend']) && @ctype_digit($_GET['job_id']))) { user_error("wrong GET parameters"); die; } $Job = new Job($_GET['job_prepend'], $_GET['job_id']); $array = ['1', '2', '3']; function countdown(string $value, int $key) { sleep(1); } $Job->foreach("countdown", $array); class Job { private $cookie_prepend = ""; private $job_id = 0; public function __construct(string $cookie_prepend, int $job_id) { $this->cookie_prepend = $cookie_prepend; $this->job_id = $job_id; } public function foreach(string $funcname, array $array) { $this->delete_old_job_cookies(); $count = count($array); setcookie("{$this->cookie_prepend}{$this->job_id}", $count); ob_implicit_flush(); $resource = fopen("php://output", "w"); foreach ($array as $key => $value) { $funcname($value, $key); fwrite($resource, " ", 1); } fclose($resource); } private function delete_old_job_cookies() { $pattern = '/^'.$this->cookie_prepend.'[0-9]+$/'; foreach ($_COOKIE as $name => $value) { if (preg_match($pattern, $name) == 1) { setcookie($name, ""); } } } } xak второй: Пока не завершено соединение мы не можем извлечь полученные данные, но можем отследить факт их получения отлавливая событие progress. А с помощью куки можно передать сколько ждать порций данных. В целом, по теме всё. Жевать не стану. Заинтересованным читать исходники, там всё понятно написано. PS: Можно не нулить буфер в настройках, можно отсылать блок размером в буфер, эфект будет тот же.