Bootstrap индикатор выполнения не показывает прогресс? - PullRequest
2 голосов
/ 29 января 2020

У меня очень длинный l oop в JS (2M + итерации).
Я хотел бы показать некоторый прогресс пользователю.
По какой-то причине код зависает и показывает только прогресс когда l oop закончено.
Что я делаю не так?

МОЖЕТ ПОЛУЧИТЬ ОСТОРОЖНЫЙ БРАУЗЕР, УСТАНАВЛИВАЙТЕ МАКСИМАЛЬНО В СООТВЕТСТВИИ

var MAX = 100000;

function doIt(){        
      alert('start count :'+ MAX);
       for(i=0;i<MAX;i++){
          setTimeout(updateBar, 600 , i);
          console.log(i);
       }        
      alert('count ended');
}


function updateBar(idx) {
    var bar = document.getElementById('progress');
     bar.style.width = Math.floor(100 * idx / MAX) + '%';
}
body {padding: 50px;}
<link href="http://getbootstrap.com/2.3.2/assets/css/bootstrap.css" rel="stylesheet"/>

<button onclick="doIt()">Start counting</button>


<div class="progress">
    <div class="bar" id="progress"></div>
</div>

Ответы [ 2 ]

2 голосов
/ 29 января 2020

Это классическая c проблема, возникающая в результате длительной работы функции, которая периодически не останавливается для передачи управления обратно в событие браузера L oop, где браузер может выполнять другие задачи, такие как рисование элементы:

Недостатком этой модели является то, что если сообщение занимает слишком много времени, веб-приложение не может обрабатывать взаимодействия с пользователем, такие как щелчок или прокрутка. Браузер смягчает это с помощью диалога «скрипт слишком долго запускается». Хорошая практика заключается в том, чтобы сделать обработку сообщений короткой и по возможности разделить одно сообщение на несколько сообщений.

Модель параллелизма и событие l oop - MDN

Если в браузере нет «передышки», потому что ваш JS l oop имеет контроль, браузер не может нарисовать индикатор выполнения.

Чтобы это исправить, вы вам нужно запустить l oop порциями, то есть «разрезать одно сообщение на несколько сообщений», как рекомендует MDN. Чтобы выполнить это sh, вам нужно будет использовать что-то асинхронное, например тайм-аут, чтобы браузер запустил следующую итерацию не "сразу", а "как только вы будете готовы". Это позволяет браузеру выполнять другие важные действия до того, как ваша функция будет продолжена (например, обрабатывать прокрутки и щелчки, а также рисовать такие элементы, как индикатор выполнения).


Решение

Следующее решение не работает l oop, возвращая управление браузеру с setTimeout(fn, 0) каждые x итераций.

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

const MAX = 500000;
let currentIteration = 0;

function doWork(){
       while (currentIteration < MAX) {
        // Do your actual work here, before the increment
        currentIteration++;
        if (currentIteration % 5000 === 0) {
          console.log(currentIteration);
          updateBar(currentIteration);
          setTimeout(doWork, 0);
          return;
        }
       }   
      alert('count ended');
}

function startWork() {
      alert('start count :'+ MAX);
      doWork();
}

function updateBar(idx) {
    var bar = document.getElementById('progress');
     bar.style.width = Math.floor(100 * idx / MAX) + '%';
}
body {padding: 50px;}
<link href="http://getbootstrap.com/2.3.2/assets/css/bootstrap.css" rel="stylesheet"/>

<button onclick="startWork()">Start counting</button>


<div class="progress">
    <div class="bar" id="progress"></div>
</div>
2 голосов
/ 29 января 2020

Вот возможное решение с использованием Web Worker :

enter image description here

worker-script. js (отдельный контекст, отдельный файл)

// When this worker receives a message
self.addEventListener("message", onMessageReceive);

function onMessageReceive(e) {
  // Get the max value
  var max = e.data.value;
  // Run the calculation
  doIt(max);
}

function doIt(max) {
  // Send messages to the parent from time to time
  self.postMessage({ type: "start", value: max });
  for (i = 0; i < max; i++) {
    if (i % 5000 === 0) { // Every 5000 iterations - tweak this
      self.postMessage({ type: "progress", value: i });
    }
  }
  self.postMessage({ type: "end", value: max });
}

main. js (на вашей странице)

var MAX = 20000000, // 20 millions, no problem
  worker = null;

// If workers are supported, create one by passing it the script url
if (window.Worker) {
  worker = new Worker("worker-script.js");
  document
    .getElementById("start-btn")
    .addEventListener("click", startWorker);
} else {
  alert("Workers are not supported in your browser.");
}

// To start the worker, send it the max value
function startWorker() {
  worker.postMessage({ value: MAX });
}

// When the worker sends a message back
worker.addEventListener("message", onMessageReceive);

function onMessageReceive(e) {
  var data = e.data;
  switch (data.type) {
    case "start":
      console.log(`start count: ${data.value}`);
      break;
    case "progress":
      updateBar(data.value);
      break;
    case "end":
      updateBar(data.value);
      console.log(`end count: ${data.value}`);
      break;
  }
}

function updateBar(idx) {
  var bar = document.getElementById("progress");
  bar.style.width = Math.floor((100 * idx) / MAX) + "%";
}

index. html

<button id="start-btn">Start counting</button>

<div class="progress">
  <div class="bar" id="progress"></div>
</div>

<script src="main.js"></script>
...