Maison  >  Article  >  interface Web  >  Explication détaillée du rôle de l'objet de promesse de jQuery dans la programmation asynchrone JavaScript_jquery

Explication détaillée du rôle de l'objet de promesse de jQuery dans la programmation asynchrone JavaScript_jquery

WBOY
WBOYoriginal
2016-05-16 15:02:241328parcourir

Promesse, le chinois peut être compris comme un souhait, représentant le résultat final d'une seule opération. Une promesse a trois états : non réalisé (non réalisé), réalisé (satisfait) et échoué (échoué). Les états réalisé et échoué peuvent être surveillés. Un souhait peut passer d'un état insatisfait à un état satisfait ou échoué. Une fois qu'un souhait est dans un état satisfait ou échoué, son statut ne peut pas être modifié. Cette fonctionnalité "immuable" est très importante pour une promesse. Elle peut empêcher l'auditeur d'état de la promesse de modifier l'état d'une promesse et provoquer un comportement anormal des autres auditeurs. Par exemple : si un écouteur qui surveille l'état réalisé change l'état de la promesse en échec, alors l'écouteur en état d'échec sera déclenché. Si un écouteur en état d'échec définit l'état de la promesse sur accompli, alors l'écouteur. à l'état rempli sera déclenché, ce qui entraînera une boucle infinie. Une autre façon de comprendre les caractéristiques de Promise est de considérer Promise comme une variable de type primitif en JavaScript. Cette variable peut être transmise à la fonction appelée, mais ne peut pas être modifiée par la fonction appelante.

Chaque objet Promise possède une méthode : then(fulfilledHandler, errorHandler, progressHandler), qui est utilisée pour surveiller les différents états d'une Promise. filledHandler est utilisé pour écouter les événements exécutés, errorHandler est utilisé pour écouter les événements ayant échoué et progressHandler est utilisé pour écouter les événements de progression. Une promesse ne force pas la mise en œuvre de la surveillance des événements d'état de progression (le Deferred de jQuery est une implémentation de Promise, mais n'implémente pas le traitement des événements d'état de progression).

La valeur de retour defilledHandler et errorHandler dans la fonction then(...) est un nouvel objet Promise afin que la fonction then(...) puisse être appelée dans une chaîne. Chaque fonction de rappel renvoie une promesse dans l'état rempli dans des circonstances normales. Si la fonction de rappel renvoie une valeur d'erreur, l'état de promesse renvoyé deviendra un échec.

Le rôle de la promesse dans la programmation asynchrone

Le mode asynchrone devient de plus en plus important dans la programmation Web. Pour le langage Web grand public Javascript, ce mode n'est pas très facile à mettre en œuvre. Pour cette raison, de nombreuses bibliothèques Javascript (telles que jQuery et Dojo) ajoutent ce qu'on appelle. C'est une abstraction de promesse (parfois aussi appelée différée). Grâce à ces bibliothèques, les développeurs peuvent utiliser le modèle de promesse dans la programmation réelle.
Avec l'approfondissement de la technologie Web 2.0, le côté navigateur subit de plus en plus de pression informatique, la « concurrence » a donc une signification positive. Pour les développeurs, ils doivent non seulement maintenir intacte l'interaction entre la page et l'utilisateur, mais également coordonner la relation entre la page et les tâches asynchrones. Ce type d'exigences de programmation d'exécution non linéaire présente des difficultés d'adaptation. En mettant de côté l'interaction des pages, nous pouvons penser à deux résultats qui doivent être traités pour les appels asynchrones : le fonctionnement réussi et le traitement des échecs. Après un appel réussi, nous devrons peut-être utiliser le résultat renvoyé dans une autre requête Ajax, ce qui provoquera une situation de « chaîne de fonctions ». Cette situation crée des complications de programmation. Jetez un œil à l'exemple de code suivant (basé sur XMLHttpRequest2) :

function searchTwitter(term, onload, onerror) {
 
   var xhr, results, url;
   url = 'http://search.twitter.com/search.json?rpp=100&q=' + term;
   xhr = new XMLHttpRequest();
   xhr.open('GET', url, true);
 
   xhr.onload = function (e) {
     if (this.status === 200) {
       results = JSON.parse(this.responseText);
       onload(results);
     }
   };
 
   xhr.onerror = function (e) {
     onerror(e);
   };
 
   xhr.send();
 }
 
 function handleError(error) {
   /* handle the error */
 }
 
 function concatResults() {
   /* order tweets by date */
 }
 
 function loadTweets() {
   var container = document.getElementById('container');
 
   searchTwitter('#IE10', function (data1) {
     searchTwitter('#IE9', function (data2) {
       /* Reshuffle due to date */
       var totalResults = concatResults(data1.results, data2.results);
       totalResults.forEach(function (tweet) {
         var el = document.createElement('li');
         el.innerText = tweet.text;
         container.appendChild(el);
       });
     }, handleError);
   }, handleError);
 }

La fonction du code ci-dessus est d'obtenir le contenu avec les hashtag IE10 et IE9 sur Twitter et de l'afficher sur la page. Ce type de fonction de rappel imbriquée est difficile à comprendre. Les développeurs doivent analyser soigneusement quel code est utilisé pour la logique métier de l'application et quel code gère les appels de fonction asynchrones. La structure du code est fragmentée. La gestion des erreurs est également décomposée. Nous devons détecter l'apparition d'erreurs à divers endroits et les gérer en conséquence.

Pour réduire la complexité de la programmation asynchrone, les développeurs recherchent des moyens simples de gérer les opérations asynchrones. L'un de ces modèles de traitement est appelé promesse et représente le résultat d'une opération qui peut être de longue durée et ne doit pas nécessairement être complète. Au lieu de bloquer et d'attendre la fin d'une longue opération, ce modèle renvoie un objet qui représente le résultat promis.

Prenons un exemple où le code de la page doit accéder à une API tierce. Les retards du réseau peuvent entraîner des temps de réponse plus longs. Dans ce cas, l'utilisation de la programmation asynchrone n'affectera pas l'interaction entre la page entière et l'utilisateur. Le mode promesse implémente généralement une méthode appelée ensuite pour enregistrer la fonction de rappel correspondante lorsque l'état change. Par exemple, l'exemple de code suivant :

searchTwitter(term).then(filterResults).then(displayResults);

promise模式在任何时刻都处于以下三种状态之一:未完成(unfulfilled)、已完成(resolved)和拒绝(rejected)。以CommonJS Promise/A 标准为例,promise对象上的then方法负责添加针对已完成和拒绝状态下的处理函数。then方法会返回另一个promise对象,以便于形成promise管道,这种返回promise对象的方式能够支持开发人员把异步操作串联起来,如then(resolvedHandler, rejectedHandler); 。resolvedHandler 回调函数在promise对象进入完成状态时会触发,并传递结果;rejectedHandler函数会在拒绝状态下调用。

有了promise模式,我们可以重新实现上面的Twitter示例。为了更好的理解实现方法,我们尝试着从零开始构建一个promise模式的框架。首先需要一些对象来存储promise。

var Promise = function () {
    /* initialize promise */
  };

接下来,定义then方法,接受两个参数用于处理完成和拒绝状态。

Promise.prototype.then = function (onResolved, onRejected) {
   /* invoke handlers based upon state transition */
 };

同时还需要两个方法来执行理从未完成到已完成和从未完成到拒绝的状态转变。

Promise.prototype.resolve = function (value) {
   /* move from unfulfilled to resolved */
 };
 
 Promise.prototype.reject = function (error) {
   /* move from unfulfilled to rejected */
 };

现在搭建了一个promise的架子,我们可以继续上面的示例,假设只获取IE10的内容。创建一个方法来发送Ajax请求并将其封装在promise中。这个promise对象分别在xhr.onload和xhr.onerror中指定了完成和拒绝状态的转变过程,请注意searchTwitter函数返回的正是promise对象。然后,在loadTweets中,使用then方法设置完成和拒绝状态对应的回调函数。

function searchTwitter(term) {

  var url, xhr, results, promise;
  url = 'http://search.twitter.com/search.json?rpp=100&q=' + term;
  promise = new Promise();
  xhr = new XMLHttpRequest();
  xhr.open('GET', url, true);

  xhr.onload = function (e) {
    if (this.status === 200) {
      results = JSON.parse(this.responseText);
      promise.resolve(results);
    }
  };

  xhr.onerror = function (e) {
    promise.reject(e);
  };

  xhr.send();
  return promise;
}

function loadTweets() {
  var container = document.getElementById('container');
  searchTwitter('#IE10').then(function (data) {
    data.results.forEach(function (tweet) {
      var el = document.createElement('li');
      el.innerText = tweet.text;
      container.appendChild(el);
    });
  }, handleError);
}

到目前为止,我们可以把promise模式应用于单个Ajax请求,似乎还体现不出promise的优势来。下面来看看多个Ajax请求的并发协作。此时,我们需要另一个方法when来存储准备调用的promise对象。一旦某个promise从未完成状态转化为完成或者拒绝状态,then方法里对应的处理函数就会被调用。when方法在需要等待所有操作都完成的时候至关重要。

Promise.when = function () {
  /* handle promises arguments and queue each */
};

以刚才获取IE10和IE9两块内容的场景为例,我们可以这样来写代码:

var container, promise1, promise2;
container = document.getElementById('container');
promise1 = searchTwitter('#IE10');
promise2 = searchTwitter('#IE9');
Promise.when(promise1, promise2).then(function (data1, data2) {

  /* Reshuffle due to date */
  var totalResults = concatResults(data1.results, data2.results);
  totalResults.forEach(function (tweet) {
    var el = document.createElement('li');
    el.innerText = tweet.text;
    container.appendChild(el);
  });
}, handleError);

分析上面的代码可知,when函数会等待两个promise对象的状态发生变化再做具体的处理。在实际的Promise库中,when函数有很多变种,比如 when.some()、when.all()、when.any()等,读者从函数名字中大概能猜出几分意思来,详细的说明可以参考CommonJS的一个promise实现when.js。

除了CommonJS,其他主流的Javascript框架如jQuery、Dojo等都存在自己的promise实现。开发人员应该好好利用这种模式来降低异步编程的复杂性。我们选取Dojo为例,看一看它的实现有什么异同。

Dojo框架里实现promise模式的对象是Deferred,该对象也有then函数用于处理完成和拒绝状态并支持串联,同时还有resolve和reject,功能如之前所述。下面的代码完成了Twitter的场景:

function searchTwitter(term) {

  var url, xhr, results, def;
  url = 'http://search.twitter.com/search.json?rpp=100&q=' + term;
  def = new dojo.Deferred();
  xhr = new XMLHttpRequest();
  xhr.open('GET', url, true);

  xhr.onload = function (e) {
    if (this.status === 200) {
      results = JSON.parse(this.responseText);
      def.resolve(results);
    }
  };

  xhr.onerror = function (e) {
    def.reject(e);
  };

  xhr.send();
  return def;
}

dojo.ready(function () {
  var container = dojo.byId('container');
  searchTwitter('#IE10').then(function (data) {
    data.results.forEach(function (tweet) {
      dojo.create('li', {
        innerHTML: tweet.text
      }, container);
    });
  });
});

不仅如此,类似dojo.xhrGet方法返回的即是dojo.Deferred对象,所以无须自己包装promise模式。

var deferred = dojo.xhrGet({
  url: "search.json",
  handleAs: "json"
});

deferred.then(function (data) {
  /* handle results */
}, function (error) {
  /* handle error */
});

除此之外,Dojo还引入了dojo.DeferredList,支持开发人员同时处理多个dojo.Deferred对象,这其实就是上面所提到的when方法的另一种表现形式。

dojo.require("dojo.DeferredList");
dojo.ready(function () {
  var container, def1, def2, defs;
  container = dojo.byId('container');
  def1 = searchTwitter('#IE10');
  def2 = searchTwitter('#IE9');

  defs = new dojo.DeferredList([def1, def2]);

  defs.then(function (data) {
    // Handle exceptions
    if (!results[0][0] || !results[1][0]) {
      dojo.create("li", {
        innerHTML: 'an error occurred'
      }, container);
      return;
    }
    var totalResults = concatResults(data[0][1].results, data[1][1].results);

    totalResults.forEach(function (tweet) {
      dojo.create("li", {
        innerHTML: tweet.text
      }, container);
    });
  });
});

上面的代码比较清楚,不再详述。

说到这里,读者可能已经对promise模式有了一个比较完整的了解,异步编程会变得越来越重要,在这种情况下,我们需要找到办法来降低复杂度,promise模式就是一个很好的例子,它的风格比较人性化,而且主流的JS框架提供了自己的实现。所以在编程实践中,开发人员应该尝试这种便捷的编程技巧。需要注意的是,promise模式的使用需要恰当地设置promise对象,在对应的事件中调用状态转换函数,并且在最后返回promise对象。


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