Chrome: tiempos de espera / intervalo suspendidos en pestañas de fondo?

130

Estaba probando la precisión del setTimeoutuso de esta prueba . Ahora noté que (como se esperaba) setTimeoutno es muy preciso, pero para la mayoría de los dispositivos no es dramáticamente inexacto. Ahora, si ejecuto la prueba en Chrome y dejo que se ejecute en una pestaña de fondo (entonces, cambiando a otra pestaña y navegando allí), volviendo a la prueba e inspeccionando los resultados (si la prueba terminó), cambian drásticamente. Parece que los tiempos de espera se han estado ejecutando mucho más lento. Probado en FF4 o IE9, esto no ocurrió.

Por lo tanto, parece que Chrome suspende o al menos ralentiza la ejecución de JavaScript en una pestaña que no tiene foco. No se pudo encontrar mucho en la red sobre el tema. Significaría que no podemos ejecutar tareas en segundo plano, como por ejemplo verificar periódicamente en un servidor usando llamadas XHR y setInterval(sospecho que veré el mismo comportamiento setInterval, escribiré una prueba si el tiempo es mío).

¿Alguien ha encontrado esto? ¿Habría una solución para esta suspensión / desaceleración? ¿Lo llamarías un error y debería archivarlo como tal?

KooiInc
fuente
¡Interesante! ¿Puedes decir si Chrome está pausando y reanudando el temporizador o reiniciándolo una vez que vuelves a acceder a la pestaña? ¿O es el comportamiento al azar? ¿Podría tener algo que ver con el hecho de que Chrome ejecuta pestañas en procesos independientes?
HyderA
@gAMBOOKa: mira la respuesta de @ pimvdb. Es probable que disminuya la velocidad a un máximo de una vez por segundo.
KooiInc
4 años después y este problema aún existe. Tengo un setTimeOut para divs con a transition, por lo que no todos los divs hacen la transición al mismo tiempo, pero en realidad 15 ms después de cada uno, creando un efecto de balanceo. Cuando voy a otra pestaña y regreso después de un tiempo, todos los divs hacen la transición al mismo tiempo y setTimeOutse ignora por completo. No es un gran problema para mi proyecto, pero es una adición extraña y no deseada.
Rvervuurt
Para nuestra animación que llamó a setTimeout en una secuencia, la solución para nosotros fue asegurarnos de recordar el identificador / identificador del temporizador (se devuelve desde setTimeout) y antes de configurar un nuevo temporizador primero llamamos a clearTimeout si hemos Tengo el mango. En nuestro caso, esto significa que cuando regrese a la pestaña, puede haber cierta extrañeza inicial en términos de qué animación se está reproduciendo, pero se resuelve bastante rápido y se reanuda la animación regular. Habíamos pensado que esto era un problema con nuestro código inicialmente.
Acción Dan

Respuestas:

88

Recientemente pregunté sobre esto y es el comportamiento por diseño. Cuando una pestaña está inactiva, solo se llama a la función como máximo una vez por segundo. Aquí está el cambio de código .

Quizás esto ayude: ¿Cómo puedo hacer que setInterval también funcione cuando una pestaña está inactiva en Chrome?

TL; DR: use Web Workers .

pimvdb
fuente
3
gracias, debería haber buscado con 'pestaña inactiva'. No ser un hablante nativo de inglés es una desventaja a veces.
KooiInc
1
@Kooilnc: No hay problema :) Tampoco soy un hablante nativo de inglés.
pimvdb
22

Hay una solución para usar Web Workers, porque se ejecutan en procesos separados y no se ralentizan

He escrito un pequeño script que puede usarse sin cambios en su código; simplemente anula las funciones setTimeout, clearTimeout, setInterval, clearInterval

Solo inclúyalo antes de todo su código

http://github.com/turuslan/HackTimer

Ruslan Tushov
fuente
77
Eso es bueno y todo, pero tenga en cuenta que: 1. Los trabajadores no tienen acceso al DOM, 2. Los trabajadores solo se ejecutan si están en un archivo por su cuenta. Es no un reemplazo directo para setTimeout para una gran cantidad de casos.
El fantasma de Madara el
1
Tiene razón, pero algunos navegadores modernos permiten utilizar trabajadores sin sus propios archivos mediante Blobs ( html5rocks.com/en/tutorials/workers/basics/#toc-inlineworkers )
Ruslan Tushov el
1
Incluso con eso, a los trabajadores web les falta mucha funcionalidad (a saber, DOM) que les permite ser un reemplazo seguro para setTimeout y compañía.
El fantasma de Madara el
¿Qué pasa con el código que tiene que ejecutarse en el front-end, por ejemplo, tareas de procesamiento de gráficos pesados ​​que nos gustaría terminar mientras hacemos otras cosas?
Michael
Bueno, puede crear Trabajadores, trabajadores de servicio y usar la API de lienzo utilizando una URL de datos. new Worker('data:text/javascript,(' + function myWorkerCode () { /*...*/ } + '()'). También es una buena manera de verificar si tiene soporte para expresiones de importación:try { eval('import("data:text/javascript,void 0")') } catch (e) { /* no support! */ }
Fábio Santos
9

Reproducir un sonido ~ vacío obliga al navegador a retener el rendimiento. Lo descubrí después de leer este comentario: ¿Cómo hacer que JavaScript se ejecute a velocidad normal en Chrome incluso cuando la pestaña no está activa?

Necesito un rendimiento ilimitado a pedido para un juego de navegador que usa WebSockets, así que sé por experiencia que el uso de WebSockets no garantiza un rendimiento ilimitado, pero a partir de las pruebas, reproducir un archivo de audio parece garantizarlo.

Aquí hay 2 bucles de audio vacíos que creé para este propósito, puede usarlos libremente, comercialmente: http://adventure.land/sounds/loops/empty_loop_for_js_performance.ogg http://adventure.land/sounds/loops/empty_loop_for_js_performance.wav

(Incluyen ruido de -58db, -60db no funciona)

Los juego, a pedido del usuario, con Howler.js: https://github.com/goldfire/howler.js

function performance_trick()
{
    if(sounds.empty) return sounds.empty.play();
    sounds.empty = new Howl({
        src: ['/sounds/loops/empty_loop_for_js_performance.ogg','/sounds/loops/empty_loop_for_js_performance.wav'],
        volume:0.5,
        autoplay: true, loop: true,
    });
}

Es triste que no haya un método incorporado para activar / desactivar el rendimiento completo de JavaScript de forma predeterminada, sin embargo, los mineros de cifrado pueden secuestrar todos sus hilos informáticos utilizando Web Workers sin ningún aviso: |

Kaan Soral
fuente
Gracias, 58db es muy fácil de escuchar con auriculares, pero silenciar el sitio resuelve ese problema
Kaan Soral
1

He lanzado el paquete npm de intervalo de trabajo que establece la implementación de Interval y clearInterval con el uso de Web-Workers para mantenerse en funcionamiento en pestañas inactivas para Chrome, Firefox e IE.

La mayoría de los navegadores modernos (Chrome, Firefox e IE), los intervalos (temporizadores de ventana) están sujetos a disparar no más de una vez por segundo en pestañas inactivas.

Puedes encontrar más información sobre

https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/setInterval

https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Using_web_workers#Timeouts_and_intervals

gorkemcnr
fuente
0

Actualicé mi núcleo jQuery a 1.9.1, y resolvió la discrepancia de intervalo en pestañas inactivas. Primero lo intentaría, luego buscaría otras opciones de anulación de código.

Carey Estes
fuente
¿desde qué versión actualizaste? Experimenté algunos problemas de tiempo de espera (controles deslizantes de la galería) con la versión ~ 1.6
dmi3y
0

Aquí está mi solución que obtiene el milisegundo actual y lo compara con el milisegundo en que se creó la función. por intervalo, actualizará el milisegundo cuando ejecute la función. También puede tomar el intervalo / tiempo de espera por una identificación.

<script>

var nowMillisTimeout = [];
var timeout = [];
var nowMillisInterval = [];
var interval = [];

function getCurrentMillis(){
    var d = new Date();
    var now = d.getHours()+""+d.getMinutes()+""+d.getSeconds()+""+d.getMilliseconds();
    return now;
}

function setAccurateTimeout(callbackfunction, millis, id=0){
    nowMillisTimeout[id] = getCurrentMillis();
    timeout[id] = setInterval(function(){ var now = getCurrentMillis(); if(now >= (+nowMillisTimeout[id] + +millis)){callbackfunction.call(); clearInterval(timeout[id]);} }, 10);
}

function setAccurateInterval(callbackfunction, millis, id=0){
    nowMillisInterval[id] = getCurrentMillis();
    interval[id] = setInterval(function(){ var now = getCurrentMillis(); if(now >= (+nowMillisInterval[id] + +millis)){callbackfunction.call(); nowMillisInterval[id] = getCurrentMillis();} }, 10);
}

//usage
setAccurateTimeout(function(){ console.log('test timeout'); }, 1000, 1);

setAccurateInterval(function(){ console.log('test interval'); }, 1000, 1);

</script>
SwiftNinjaPro
fuente