Maison  >  Article  >  interface Web  >  Compréhension approfondie des mécanismes de synchronisation et asynchrones en programmation JavaScript_Connaissances de base

Compréhension approfondie des mécanismes de synchronisation et asynchrones en programmation JavaScript_Connaissances de base

WBOY
WBOYoriginal
2016-05-16 15:53:061321parcourir

L'une des forces de JavaScript réside dans la façon dont il gère le code asynchrone. Le code asynchrone est placé dans une file d'attente d'événements et attend que tous les autres codes soient exécutés sans bloquer le thread. Cependant, écrire du code asynchrone peut être difficile pour les débutants. Et dans cet article, je vais dissiper toute confusion que vous pourriez avoir.
Comprendre le code asynchrone

Les fonctions asynchrones les plus basiques en JavaScript sont setTimeout et setInterval. setTimeout exécutera la fonction donnée après une certaine période de temps. Il accepte une fonction de rappel comme premier paramètre et une durée en millisecondes comme deuxième paramètre. Voici des exemples d'utilisation :

console.log( "a" );
setTimeout(function() {
  console.log( "c" )
}, 500 );
setTimeout(function() {
  console.log( "d" )
}, 500 );
setTimeout(function() {
  console.log( "e" )
}, 500 );
console.log( "b" );

Comme prévu, la console affiche d'abord "a" et "b", et environ 500 millisecondes plus tard, "c", "d" et "e" sont visibles. J'utilise "environ" car setTimeout est en réalité imprévisible. En fait, même la spécification HTML5 mentionne ce problème :

  • "Cette API ne garantit pas que le timing s'exécutera avec précision comme prévu. Des retards dus à la charge du processeur, à d'autres tâches, etc. sont à prévoir. "

Il est intéressant de noter que le délai d'attente ne se produit que lorsque tout le code restant dans le même segment a fini de s'exécuter. Ainsi, si un délai d'attente est défini et qu'une fonction de longue durée est exécutée, le délai d'attente ne démarrera même pas tant que la fonction ne sera pas terminée. En fait, les fonctions asynchrones, telles que setTimeout et setInterval, sont placées dans une file d'attente appelée Event Loop.

Event Loop est une file d'attente de fonctions de rappel. Lorsque la fonction asynchrone est exécutée, la fonction de rappel sera poussée dans cette file d'attente. Le moteur JavaScript ne commencera pas à traiter la boucle d'événements tant que l'exécution de la fonction asynchrone ne sera pas terminée. Cela signifie que le code JavaScript n'est pas multithread, même s'il se comporte de manière similaire. La boucle d'événements est une file d'attente premier entré, premier sorti (FIFO), ce qui signifie que les rappels sont exécutés dans l'ordre dans lequel ils sont mis en file d'attente. JavaScript a été choisi comme langage de développement pour Node en raison de la facilité avec laquelle il est possible d'écrire un tel code.

Ajax

Javascript asynchrone et XML (AJAX) ont définitivement changé le paysage du langage Javascript. Du coup, le navigateur n’a plus besoin de recharger pour mettre à jour les pages web. Le code pour implémenter Ajax dans différents navigateurs peut être long et fastidieux ; cependant, grâce à l'aide de jQuery (et d'autres bibliothèques), nous pouvons réaliser une communication client-serveur de manière simple et élégante.

Nous pouvons facilement récupérer des données à l'aide de l'interface multi-navigateurs jQuery $.ajax, mais nous ne pouvons pas montrer ce qui se passe dans les coulisses. Par exemple :

var data;
$.ajax({
  url: "some/url/1",
  success: function( data ) {
    // But, this will!
    console.log( data );
  }
})
// Oops, this won't work...
console.log( data );

L'erreur la plus courante est d'utiliser les données immédiatement après avoir appelé $.ajax, mais en fait c'est comme ceci :



xmlhttp.open( "GET", "some/ur/1", true );
xmlhttp.onreadystatechange = function( data ) {
  if ( xmlhttp.readyState === 4 ) {
    console.log( data );
  }
};
xmlhttp.send( null );
L'objet XmlHttpRequest sous-jacent initie une requête et définit une fonction de rappel pour gérer l'événement readystatechnage de XHR. Exécutez ensuite la méthode d’envoi de XHR. Lorsque XHR est en cours d'exécution, l'événement readystatechange sera déclenché lorsque son attribut readyState change, et la fonction de rappel déclenchera l'exécution uniquement lorsque XHR cessera de recevoir une réponse du serveur distant.

Gestion du code asynchrone

La programmation asynchrone peut facilement tomber dans ce que nous appelons souvent « l'enfer des rappels ». Parce qu'en fait, presque toutes les fonctions asynchrones de JS utilisent des rappels, le résultat de l'exécution continue de plusieurs fonctions asynchrones est des couches de fonctions de rappel imbriquées et le code complexe qui en résulte.

De nombreuses fonctions dans node.js sont également asynchrones. Par conséquent, le code suivant est fondamentalement très courant :



var fs = require( "fs" );
fs.exists( "index.js", function() {
  fs.readFile( "index.js", "utf8", function( err, contents ) {
    contents = someFunction( contents ); // do something with contents
    fs.writeFile( "index.js", "utf8", function() {
      console.log( "whew! Done finally..." );
    });
  });
});
console.log( "executing..." );
Le code client suivant est également courant :


GMaps.geocode({
  address: fromAddress,
  callback: function( results, status ) {
    if ( status == "OK" ) {
      fromLatLng = results[0].geometry.location;
      GMaps.geocode({
        address: toAddress,
        callback: function( results, status ) {
          if ( status == "OK" ) {
            toLatLng = results[0].geometry.location;
            map.getRoutes({
              origin: [ fromLatLng.lat(), fromLatLng.lng() ],
              destination: [ toLatLng.lat(), toLatLng.lng() ],
              travelMode: "driving",
              unitSystem: "imperial",
              callback: function( e ){
                console.log( "ANNNND FINALLY here's the directions..." );
                // do something with e
              }
            });
          }
        }
      });
    }
  }
});
Les rappels imbriqués peuvent devenir vraiment désagréables, mais il existe plusieurs solutions à ce style de codage.

Les rappels imbriqués peuvent facilement apporter une « mauvaise odeur » dans le code, mais vous pouvez utiliser les styles suivants pour essayer de résoudre ce problème

    Le problème ne vient pas du langage lui-même ; mais de la façon dont les programmeurs l'utilisent : Async Javascript.
Il n'y a pas de mauvais langages, seulement de mauvais programmeurs - JavaScript asynchrone


Fonction nommée

Une solution pratique pour effacer les rappels imbriqués consiste simplement à éviter plus de deux niveaux d'imbrication. Passez une fonction nommée comme paramètre de rappel au lieu de passer une fonction anonyme :



var fromLatLng, toLatLng;
var routeDone = function( e ){
  console.log( "ANNNND FINALLY here's the directions..." );
  // do something with e
};
var toAddressDone = function( results, status ) {
  if ( status == "OK" ) {
    toLatLng = results[0].geometry.location;
    map.getRoutes({
      origin: [ fromLatLng.lat(), fromLatLng.lng() ],
      destination: [ toLatLng.lat(), toLatLng.lng() ],
      travelMode: "driving",
      unitSystem: "imperial",
      callback: routeDone
    });
  }
};
var fromAddressDone = function( results, status ) {
  if ( status == "OK" ) {
    fromLatLng = results[0].geometry.location;
    GMaps.geocode({
      address: toAddress,
      callback: toAddressDone
    });
  }
};
GMaps.geocode({
  address: fromAddress,
  callback: fromAddressDone
});
De plus, la bibliothèque async.js peut nous aider à gérer plusieurs requêtes/réponses Ajax. Par exemple :



async.parallel([
  function( done ) {
    GMaps.geocode({
      address: toAddress,
      callback: function( result ) {
        done( null, result );
      }
    });
  },
  function( done ) {
    GMaps.geocode({
      address: fromAddress,
      callback: function( result ) {
        done( null, result );
      }
    });
  }
], function( errors, results ) {
  getRoute( results[0], results[1] );
});

这段代码执行两个异步函数,每个函数都接收一个名为"done"的回调函数并在函数结束的时候调用它。当两个"done"回调函数结束后,parallel函数的回调函数被调用并执行或处理这两个异步函数产生的结果或错误。

Promises模型
引自 CommonJS/A:

  •     promise表示一个操作独立完成后返回的最终结果。

有很多库都包含了promise模型,其中jQuery已经有了一个可使用且很出色的promise API。jQuery在1.5版本引入了Deferred对象,并可以在返回promise的函数中使用jQuery.Deferred的构造结果。而返回promise的函数则用于执行某种异步操作并解决完成后的延迟。
 

var geocode = function( address ) {
  var dfd = new $.Deferred();
  GMaps.geocode({
    address: address,
    callback: function( response, status ) {
      return dfd.resolve( response );
    }
  });
  return dfd.promise();
};
var getRoute = function( fromLatLng, toLatLng ) {
  var dfd = new $.Deferred();
  map.getRoutes({
    origin: [ fromLatLng.lat(), fromLatLng.lng() ],
    destination: [ toLatLng.lat(), toLatLng.lng() ],
    travelMode: "driving",
    unitSystem: "imperial",
    callback: function( e ) {
      return dfd.resolve( e );
    }
  });
  return dfd.promise();
};
var doSomethingCoolWithDirections = function( route ) {
  // do something with route
};
$.when( geocode( fromAddress ), geocode( toAddress ) ).
  then(function( fromLatLng, toLatLng ) {
    getRoute( fromLatLng, toLatLng ).then( doSomethingCoolWithDirections );
  });

这允许你执行两个异步函数后,等待它们的结果,之后再用先前两个调用的结果来执行另外一个函数。

  •     promise表示一个操作独立完成后返回的最终结果。

在这段代码里,geocode方法执行了两次并返回了一个promise。异步函数之后执行,并在其回调里调用了resolve。然后,一旦两次调用resolve完成,then将会执行,其接收了之前两次调用geocode的返回结果。结果之后被传入getRoute,此方法也返回一个promise。最终,当getRoute的promise解决后,doSomethingCoolWithDirections回调就执行了。
 
事件
事件是另一种当异步回调完成处理后的通讯方式。一个对象可以成为发射器并派发事件,而另外的对象则监听这些事件。这种类型的事件处理方式称之为 观察者模式 。 backbone.js 库在withBackbone.Events中就创建了这样的功能模块。
 

var SomeModel = Backbone.Model.extend({
  url: "/someurl"
});
var SomeView = Backbone.View.extend({
  initialize: function() {
    this.model.on( "reset", this.render, this );
    this.model.fetch();
  },
  render: function( data ) {
    // do something with data
  }
});
var view = new SomeView({
  model: new SomeModel()
});

还有其他用于发射事件的混合例子和函数库,例如 jQuery Event Emitter , EventEmitter , monologue.js ,以及node.js内建的 EventEmitter 模块。

  •     事件循环是一个回调函数的队列。

一个类似的派发消息的方式称为 中介者模式 , postal.js 库中用的即是这种方式。在中介者模式,有一个用于所有对象监听和派发事件的中间人。在这种模式下,一个对象不与另外的对象产生直接联系,从而使得对象间都互相分离。

绝不要返回promise到一个公用的API。这不仅关系到了API用户对promises的使用,也使得重构更加困难。不过,内部用途的promises和外部接口的事件的结合,却可以让应用更低耦合且便于测试。

在先前的例子里面,doSomethingCoolWithDirections回调函数在两个geocode函数完成后执行。然后,doSomethingCoolWithDirections才会获得从getRoute接收到的响应,再将其作为消息发送出去。
 

var doSomethingCoolWithDirections = function( route ) {
  postal.channel( "ui" ).publish( "directions.done", {
    route: route
  });
};

这允许了应用的其他部分不需要直接引用产生请求的对象,就可以响应异步回调。而在取得命令时,很可能页面的好多区域都需要更新。在一个典型的jQuery Ajax过程中,当接收到的命令变化时,要顺利的回调可能就得做相应的调整了。这可能会使得代码难以维护,但通过使用消息,处理UI多个区域的更新就会简单得多了。
 

var UI = function() {
  this.channel = postal.channel( "ui" );
  this.channel.subscribe( "directions.done", this.updateDirections ).withContext( this );
};
UI.prototype.updateDirections = function( data ) {
  // The route is available on data.route, now just update the UI
};
app.ui = new UI();

另外一些基于中介者模式传送消息的库有 amplify, PubSubJS, and radio.js。

结论

JavaScript 使得编写异步代码很容易. 使用 promises, 事件, 或者命名函数来避免“callback hell”. 为获取更多javascript异步编程信息,请点击Async JavaScript: Build More Responsive Apps with Less . 更多的实例托管在github上,地址NetTutsAsyncJS,赶快Clone吧 !

Déclaration:
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn