web-dev-qa-db-fra.com

Qu'est-ce que Ember RunLoop et comment ça marche?

J'essaie de comprendre comment Ember RunLoop fonctionne et ce qui le fait fonctionner. J'ai regardé la documentation , mais j'ai encore beaucoup de questions à ce sujet. Je suis intéressé par mieux comprendre comment RunLoop fonctionne afin que je puisse choisir la méthode appropriée dans son espace de nom, quand je dois différer l'exécution de certains codes pour une date ultérieure.

  • Quand Ember RunLoop démarre-t-il. Il dépend du routeur ou des vues ou des contrôleurs ou autre chose?
  • combien de temps cela prend approximativement (je sais que c'est assez idiot de demander et dépend de beaucoup de choses mais je cherche une idée générale, ou peut-être s'il y a un temps minimum ou maximum qu'un runloop peut prendre)
  • RunLoop est-il exécuté à tout moment ou indique-t-il simplement une période de temps du début à la fin de l'exécution et peut ne pas s'exécuter pendant un certain temps?.
  • Si une vue est créée à partir d'un RunLoop, est-il garanti que tout son contenu en fera le DOM au moment où la boucle se termine?

Pardonnez-moi si ce sont des questions très basiques, je pense que les comprendre aidera les noobs comme moi à mieux utiliser Ember.

96
Aras

Mise à jour 10/9/2013: Découvrez cette visualisation interactive de la boucle d'exécution: https://machty.s3.amazonaws.com /ember-run-loop-visual/index.html

Mise à jour 5/9/2013: tous les concepts de base ci-dessous sont toujours à jour, mais en date de ce commit , le Ember L'implémentation de la boucle d'exécution a été divisée en une bibliothèque distincte appelée backburner.js , avec quelques différences API très mineures.

Tout d'abord, lisez-les:

http://blog.sproutcore.com/the-run-loop-part-1/

http://blog.sproutcore.com/the-run-loop-part-2/

Ils ne sont pas précis à 100% pour Ember, mais les concepts de base et la motivation derrière le RunLoop s'appliquent généralement à Ember; seuls certains détails de mise en œuvre diffèrent. Mais, pour vos questions:

Quand Ember RunLoop démarre-t-il. Il dépend du routeur ou des vues ou des contrôleurs ou autre chose?

Tous les événements utilisateur de base (par exemple, événements clavier, événements souris, etc.) déclencheront la boucle d'exécution. Cela garantit que toutes les modifications apportées aux propriétés liées par l'événement capturé (souris/clavier/minuterie/etc.) sont entièrement propagées à travers le système de liaison de données d'Ember avant de retourner le contrôle au système. Donc, en déplaçant votre souris, en appuyant sur une touche, en cliquant sur un bouton, etc., tout lance la boucle de course.

combien de temps cela prend approximativement (je sais que c'est assez idiot de demander et dépend de beaucoup de choses mais je cherche une idée générale, ou peut-être s'il y a un temps minimum ou maximum qu'un runloop peut prendre)

À aucun moment, le RunLoop ne gardera trace du temps qu'il faut pour propager toutes les modifications à travers le système, puis arrêter le RunLoop après avoir atteint une limite de temps maximale; au lieu de cela, RunLoop s'exécutera toujours jusqu'à son terme et ne s'arrêtera pas tant que tous les temporisateurs expirés n'auront pas été appelés, les liaisons propagées, et peut-être leur les liaisons propagées, etc. De toute évidence, plus il y a de changements à propager à partir d'un seul événement, plus le RunLoop mettra longtemps à se terminer. Voici un exemple (assez injuste) de la façon dont le RunLoop peut s'enliser avec la propagation des modifications par rapport à un autre framework (Backbone) qui n'a pas de boucle d'exécution: http://jsfiddle.net/jashkenas/CGSd5/ . Morale de l'histoire: le RunLoop est très rapide pour la plupart des choses que vous voudriez faire dans Ember, et c'est là que réside la plus grande partie de la puissance d'Ember, mais si vous vous trouvez à vouloir animer 30 cercles avec Javascript à 60 images par seconde, là pourrait être de meilleures façons de s'y prendre que de compter sur RunLoop d'Ember.

RunLoop est-il exécuté à tout moment ou indique-t-il simplement une période de temps du début à la fin de l'exécution et peut ne pas s'exécuter pendant un certain temps?.

Elle n'est pas exécutée à tout moment - elle doit rendre le contrôle au système à un moment ou sinon votre application se bloquerait - c'est différent, disons, d'une boucle d'exécution sur un serveur qui a un while(true) et continue à l'infini jusqu'à ce que le serveur reçoive un signal pour s'arrêter ... le Ember RunLoop n'a pas de tel while(true) mais est uniquement tourné en réponse aux événements utilisateur/timer) .

Si une vue est créée à partir d'un RunLoop, est-il garanti que tout son contenu en fera le DOM au moment où la boucle se termine?

Voyons voir si nous pouvons comprendre cela. L'un des grands changements de SC à Ember RunLoop est que, au lieu de faire des allers-retours entre invokeOnce et invokeLast (que vous voyez dans le diagramme dans le premier lien sur le RL de SproutCore), Ember vous fournit une liste de 'files d'attente' qui, au cours d'une boucle d'exécution, vous pouvez planifier des actions ( fonctions à appeler pendant la boucle d'exécution) en spécifiant dans quelle file d'attente l'action appartient (exemple de la source: Ember.run.scheduleOnce('render', bindView, 'rerender');).

Si vous regardez run_loop.js Dans le code source, vous voyez Ember.run.queues = ['sync', 'actions', 'destroy', 'timers'];, Mais si vous ouvrez votre débogueur JavaScript dans le navigateur dans une application Ember et évaluez Ember.run.queues, Vous obtenez une liste plus complète de files d'attente: ["sync", "actions", "render", "afterRender", "destroy", "timers"]. Ember conserve leur base de code assez modulaire, et ils rendent possible votre code, ainsi que son propre code dans une partie distincte de la bibliothèque, pour insérer plus de files d'attente. Dans ce cas, la bibliothèque Ember Views insère les files d'attente render et afterRender, en particulier après la file d'attente actions. Je vais comprendre pourquoi cela pourrait se produire dans une seconde. Premièrement, l'algorithme RunLoop:

L'algorithme RunLoop est à peu près le même que celui décrit dans les articles sur la boucle d'exécution SC ci-dessus:

  • Vous exécutez votre code entre RunLoop .begin() et .end(), uniquement en Ember vous voudrez plutôt exécuter votre code dans Ember.run, Qui appellera en interne begin et end pour vous. (Seul le code de boucle d'exécution interne dans la base de code Ember utilise toujours begin et end, vous devriez donc vous en tenir à Ember.run)
  • Après l'appel de end(), le RunLoop passe alors à la vitesse supérieure pour propager chaque modification apportée par le bloc de code passé à la fonction Ember.run. Cela inclut la propagation des valeurs des propriétés liées, le rendu des modifications de vue dans le DOM, etc. etc. L'ordre dans lequel ces actions (liaison, rendu des éléments DOM, etc.) sont effectuées est déterminé par le tableau Ember.run.queues Décrit ci-dessus:
  • La boucle d'exécution démarrera sur la première file d'attente, qui est sync. Il exécutera toutes les actions planifiées dans la file d'attente sync par le code Ember.run. Ces actions peuvent elles-mêmes également planifier d'autres actions à exécuter au cours de cette même RunLoop, et c'est à la RunLoop de s'assurer qu'elle exécute chaque action jusqu'à ce que toutes les files d'attente soient vidées. À la fin de chaque file d'attente, le RunLoop examine toutes les files d'attente précédemment vidées et vérifie si de nouvelles actions ont été planifiées. Si tel est le cas, il doit démarrer au début de la première file d'attente avec des actions planifiées non exécutées et vider la file d'attente, en continuant de suivre ses étapes et recommencer si nécessaire jusqu'à ce que toutes les files d'attente soient complètement vides.

Voilà l'essence de l'algorithme. C'est ainsi que les données liées sont propagées via l'application. Vous pouvez vous attendre à ce qu'une fois RunLoop terminé, toutes les données liées soient entièrement propagées. Alors, qu'en est-il des éléments DOM?

L'ordre des files d'attente, y compris celles ajoutées par la bibliothèque Ember Views, est important ici. Notez que render et afterRender viennent après sync et action. La file d'attente sync contient toutes les actions pour propager les données liées. (action, après cela, n'est que rarement utilisé dans le Ember source). D'après l'algorithme ci-dessus, il est garanti qu'au moment où RunLoop arrivera dans la file d'attente render, toutes les liaisons de données auront terminé la synchronisation. Ceci est voulu par la conception même du produit. : vous ne voudriez pas effectuer la tâche coûteuse de rendu des éléments DOM avant synchroniser les liaisons de données, car cela nécessiterait probablement un nouveau rendu des éléments DOM avec des données mises à jour - évidemment un manière très inefficace et sujette aux erreurs de vider toutes les files d'attente RunLoop. Donc Ember explose intelligemment tout le travail de liaison de données qu'il peut avant de rendre les éléments DOM dans le render queue.

Donc, enfin, pour répondre à votre question, oui, vous pouvez vous attendre à ce que tous les rendus DOM nécessaires aient eu lieu au moment où Ember.run Se termine. Voici un jsFiddle pour démontrer: http://jsfiddle.net/machty/6p6XJ/328/

Autres choses à savoir sur le RunLoop

Observateurs vs liaisons

Il est important de noter que les observateurs et les liaisons, tout en ayant la fonctionnalité similaire de répondre aux changements dans une propriété "surveillée", se comportent totalement différemment dans le contexte d'un RunLoop. La propagation de liaison, comme nous l'avons vu, est planifiée dans la file d'attente sync pour être éventuellement exécutée par RunLoop. Les observateurs, d'autre part, tirent immédiatement lorsque la propriété surveillée change sans avoir à être programmée pour la première fois dans une file d'attente RunLoop. Si un observateur et une liaison "surveillent" tous la même propriété, l'observateur sera toujours appelé 100% du temps avant que la liaison ne soit mise à jour.

scheduleOnce et Ember.run.once

L'un des gros gains d'efficacité dans les modèles de mise à jour automatique d'Ember est basé sur le fait que, grâce à RunLoop, plusieurs actions RunLoop identiques peuvent être fusionnées ("rebondies", si vous voulez) en une seule action. Si vous examinez les internes run_loop.js, Vous verrez que les fonctions qui facilitent ce comportement sont les fonctions connexes scheduleOnce et Em.run.once. La différence entre eux n'est pas aussi importante que de savoir qu'ils existent et de savoir comment ils peuvent ignorer les actions en double dans la file d'attente pour éviter beaucoup de calculs gonflés et inutiles pendant la boucle d'exécution.

Et les minuteries?

Même si 'timers' est l'une des files d'attente par défaut répertoriées ci-dessus, Ember fait uniquement référence à la file d'attente dans leurs cas de test RunLoop. Il semble qu'une telle file d'attente aurait été utilisée dans les jours SproutCore basé sur certaines des descriptions des articles ci-dessus sur les minuteries étant la dernière chose à déclencher. Dans Ember, la file d'attente timers n'est pas utilisée. Au lieu de cela, le RunLoop peut être tourné par un setTimeout événement (voir la fonction invokeLaterTimers), qui est suffisamment intelligent pour parcourir tous les temporisateurs existants, déclencher tous ceux qui ont expiré, déterminer le premier temporisateur futur et définir un setTimeout pour cet événement uniquement, qui fera tourner à nouveau le RunLoop lorsqu'il se déclenchera. Cette approche est plus efficace que de demander à chaque temporisateur d'appeler setTimeout et de se réveiller, car dans ce cas, un seul appel setTimeout doit être effectué, et le RunLoop est suffisamment intelligent pour déclencher tous les différents chronomètres qui pourraient se déclencher en même temps.

Anti-rebond avec la file d'attente sync

Voici un extrait de la boucle d'exécution, au milieu d'une boucle à travers toutes les files d'attente de la boucle d'exécution. Notez le cas particulier de la file d'attente sync: parce que sync est une file d'attente particulièrement volatile, dans laquelle les données sont propagées dans toutes les directions, Ember.beginPropertyChanges() est appelé pour empêcher tout observateur de être renvoyé, suivi d'un appel à Ember.endPropertyChanges. C'est sage: si au cours du vidage de la file d'attente sync, il est tout à fait possible qu'une propriété sur un objet change plusieurs fois avant de se reposer sur sa valeur finale, et vous ne voudriez pas gaspiller des ressources immédiatement licencier des observateurs à chaque changement.

if (queueName === 'sync') 
{
    log = Ember.LOG_BINDINGS;

    if (log) 
    {
        Ember.Logger.log('Begin: Flush Sync Queue');
    }

    Ember.beginPropertyChanges();
    Ember.tryFinally(tryable, Ember.endPropertyChanges);

    if (log) 
    { 
        Ember.Logger.log('End: Flush Sync Queue'); 
    }
} 
else 
{
   forEach.call(queue, iter);
}

J'espère que cela t'aides. J'ai certainement dû apprendre un peu juste pour écrire cette chose, ce qui était un peu le point.

199