J'ai une interface utilisateur où j'ai besoin d'animations pour fonctionner correctement. De temps en temps, je dois faire un calcul de données semi-volumineux qui fait sauter l'animation jusqu'à ce que ce calcul soit terminé.

J'essaie de contourner ce problème en effectuant le calcul des données asynchrone avec setTimeout. Quelque chose comme setTimeout(calcData(), 0);

Le code entier est quelque chose comme ça (simplifié):

while (animating) {
    performAnimation();
    if (needCalc) {
       setTimeout(calcData(), 0);
    }
}

Mais je reçois toujours un saut dans l'animation. Cela fonctionne bien quand je n'ai pas besoin de faire de calculs de données. Comment puis-je le faire efficacement? Merci!

-1
shell 7 mars 2016 à 04:03

3 réponses

Meilleure réponse

Vous voyez le saut car un seul thread javascript est exécuté à la fois. Lorsque quelque chose est fait de manière asynchrone, le moteur javascript place une file d'attente à exécuter plus tard, puis trouve autre chose à exécuter. Lorsque quelque chose dans la file d'attente doit être fait, le moteur le retirera et l'exécutera, bloquant toutes les autres opérations jusqu'à ce qu'il se termine.Le moteur retire ensuite autre chose de sa file d'attente pour l'exécuter.

Donc, si vous souhaitez permettre à votre rendu de fonctionner correctement, vous devez diviser votre calcul en plusieurs appels asynchrones, permettant au moteur de planifier l'opération de rendu entre les calculs. Ceci est facile à réaliser si vous ne faites qu'itérer sur un tableau, vous pouvez donc faire quelque chose comme:

var now=Date.now;
if(window.performance&&performance.now){//use performace.now if we can
    now=performance.now;
}

function calculate(){
    var batchSize=10;//If you have a exceptionally long operation you may want to make this lower.
    var i=0;
    var next=function(){
        var start=now();
        while(now()-start<14){//14ms / frame
            var end=Math.min(i+batchSize,data.length);
            for(;i<end;i++){//do batches to reduce time overhead
                do_calc(data[i]);
            }
        }
        if(i<data.length) setTimeout(next,1)//defer to next tick
    };
    next();
}
calculate();


function render(){
    do_render_stuff();
    if(animating) {
        requestAnimationFrame(render);//use requestAnimationFrame rather then setTimeout for rendering
    }
}
render();

Mieux encore, si vous le pouvez, vous devriez utiliser des WebWorkers qui fonctionnent dans un thread différent, complètement séparé du moteur js principal. Cependant, vous êtes coincé avec cela si vous devez faire quelque chose que vous ne pouvez pas faire dans un WebWorker, comme manipuler l'arborescence DOM.

1
Abex 7 mars 2016 à 02:45

Généralement, votre problème se résume à votre mauvaise utilisation de setTimeout ()

 setTimeout(calcData(), 0);

Le premier argument de setTimeout est une REFERENCE à la fonction que vous souhaitez appeler. Dans votre code, vous ne faites pas référence à la fonction calcData, vous l'appelez car vous avez inclus () après le nom de la fonction.

Deuxièmement, le fait que vous ayez mis 0 pour le délai ne signifie pas que vous aurez un délai de 0 seconde avant l'exécution de la fonction. JavaScript s'exécute dans un contexte à thread unique. La fonction setTimeout est placée dans une file d'attente et exécutée lorsque le moteur JavaScript est disponible, mais au plus tôt un minimum de 10 ms ou la quantité que vous spécifiez (selon la moindre des deux).

De façon réaliste, votre ligne devrait être:

   setTimeout(calcData(),10);
0
Scott Marcus 7 mars 2016 à 02:48

Tout d'abord , parlons de ce qui se passe dans votre code:

while (animating) {
    performAnimation();
    if (needCalc) {
       // it should be setTimeout(calcData, 0);
       setTimeout(calcData(), 0);
    }
}

Dans la ligne setTimeout(calcData(), 0);, vous ne retardez vraiment pas l'appel de la fonction calcData, vous l'appelez, car vous utilisez l'opérateur () après le nom de la fonction.

Deuxièmement , réfléchissons à ce qui se passe lorsque vous faites vraiment différer l'appel de calcData dans le code ci-dessus: généralement JavaScript s'exécute dans un seul thread, donc, si vous avez du code comme celui-ci:

setTimeout(doSomething, 0);
while (true) {};

doSomething ne sera jamais appelé, car l'interpréteur de javascript exécute while la boucle pour toujours et il n'a pas de "temps libre" pour exécuter d'autres choses (même l'interface utilisateur). setTimeout - dites simplement de planifier l'exécution de doSomething lorsque l'interprète sera libre et qu'il sera temps d'exécuter cette fonction.

Ainsi, lorsque le navigateur exécute la fonction javascript, toutes les autres choses se bloquent.

Solution :

  1. Si vous avez des données volumineuses que vous devez traiter, il serait peut-être préférable de faire des calculs sur le backend et après l'envoi des résultats au frontend.

  2. Habituellement, lorsque vous devez effectuer des calculs et afficher des résultats, il est préférable d'utiliser requestAnimationFrame que la boucle while. Le navigateur exécutera la fonction passée dans requestAnimationFrame dès que possible, mais vous donnez également au navigateur le temps de gérer d'autres événements. Vous pouvez voir un redessin fluide en utilisant requestAnimationFrame pour le jeu (tutoriel étape par étape ici).

  3. Si vous voulez vraiment traiter une énorme quantité de données au niveau de la partie frontale et que vous souhaitez rendre l'interface utilisateur fluide, vous pouvez essayer d'utiliser WebWorkers. Les WebWorkers ressemblent à des threads en JavaScript, vous devez communiquer entre le "thread" d'interface utilisateur principal et WebWorker en passant des messages de l'un à l'autre et inversement et les calculs sur WebWorker n'affectent pas le thread d'interface utilisateur.

1
Timur Bilalov 7 mars 2016 à 02:37