Я рекомендую requestIdleCallback()
как принятый ответ, но он все еще экспериментален, и мне нравится придумывать подобные вещи.Вы можете даже объединить RIC с этим ответом, чтобы получить что-то более подходящее для ваших нужд.
Первая задача - разделить ваш код бездействия на маленькие работающие куски, чтобы вы могли проверить, сколько времени вы провели / потратили между кусками.
Один из способов - создать в очереди несколько функций, которые выполняют необходимую работу, например, unprocessed.forEach(x=>workQueue.push(idleFunc.bind(null,x)));}
, затем назначить исполнителя, который в какой-то момент будет обрабатывать очередь в течение заданного промежутка времени.
Если у вас есть цикл, для завершения которого требуется некоторое время, вы можете использовать функцию генератора и выдавать в конце каждого цикла, а затем запускать его внутри рекурсивных вызовов на setTimeout()
с вашим собственным крайним сроком.или requestIdleCallback()
.
Вы также можете иметь рекурсивную функцию, которая при обработке добавит себя обратно в конец очереди, что может помочь, если вы хотите дать другое рабочее время для выполнения или при созданииФункция для каждой части работы была бы абсурдной (например, сотни элементов массива, привязанных к функции, для обработки которой требуется всего 1 мс).
Aв любом случае, вот что-то, что я вырвал из любопытства.
class IdleWorkExecutor {
constructor() {
this.workQueue=[];
this.running=null;
}
addWork(func) {
this.workQueue.push(_=>func());
this.start();
}
//
addWorkPromise(func) {
return new Promise(r=>{
this.workQueue.push(_=>r(func()));
this.start();
});
//DRY alternative with more overhead:
//return new Promise(r=>this.addWork(_=>r(func())));
}
sleep(ms) {
return new Promise(r=>setTimeout(r,ms));
}
//Only run the work loop when there is work to be done
start() {
if (this.running) {return this.running;}
return this.running=(async _=>{
//Create local reference to the queue and sleep for negligible performance gain...
const {workQueue,sleep}=this;
//Declare deadline as 0 to pause execution as soon as the loop is entered.
let deadline=0;
while (workQueue.length!==0) {
if (performance.now()>deadline) {
await sleep(10);
deadline=performance.now()+1;
}
/*shift* off and execute a piece of work. *push and shift are used to
create a FIFO buffer, but a growable ring buffer would be better. This
was chosen over unshift and pop because expensive operations shouldn't
be performed outside the idle executor.*/
workQueue.shift()(deadline);
}
this.running=false;
})();
}
}
//Trying out the class.
let executor=new IdleWorkExecutor();
executor.addWork(_=>console.log('Hello World!'));
executor.addWorkPromise(_=>1+1).then(ans=>{
executor.addWork(_=>console.log('Answer: '+ans));
});
//A recursive busy loop function.
executor.addWork(function a(counter=20) {
const deadline=performance.now()+0.2;
let i=0;
while (performance.now()<deadline) {i++}
console.log(deadline,i);
if (counter>0) {
executor.addWork(a.bind(null,counter-1));
}
});
Если вы можете использовать requestIdleCallback()
в своем коде, добавить его в IdleWorkExecutor
довольно просто:
function rICPromise(opt) {
return new Promise(r=>{
requestIdleCallback(r,opt);
});
}
if (!deadline||deadline.timeRemaining()>0) {
deadline=await rICPromise({timeout:5000});
}