Maison > Article > interface Web > Expérience pratique de Nodejs : le module eventproxy contrôle concurrency_node.js
Objectif
Créez un projet de leçon4 et écrivez-y du code.
Le point d'entrée du code est app.js. Lorsque le nœud app.js est appelé, il affichera les titres, liens et chapitres de tous les sujets sur le CNode (https://cnodejs.org/) page d'accueil de la communauté. Un commentaire, au format json.
Exemple de sortie :
[ { "title": "【公告】发招聘帖的同学留意一下这里", "href": "http://cnodejs.org/topic/541ed2d05e28155f24676a12", "comment1": "呵呵呵呵" }, { "title": "发布一款 Sublime Text 下的 JavaScript 语法高亮插件", "href": "http://cnodejs.org/topic/54207e2efffeb6de3d61f68f", "comment1": "沙发!" } ]
Défi
Sur la base de l'objectif ci-dessus, affichez l'auteur du commentaire1 et sa valeur en points dans la communauté cnode.
Exemple :
[ { "title": "【公告】发招聘帖的同学留意一下这里", "href": "http://cnodejs.org/topic/541ed2d05e28155f24676a12", "comment1": "呵呵呵呵", "author1": "auser", "score1": 80 }, ... ]
Points de connaissances
Découvrez la beauté de l'enfer des rappels de Node.js
Apprenez à utiliser eventproxy, un outil pour contrôler la concurrence
Contenu du cours
Dans ce chapitre, nous abordons la partie la plus impressionnante de Node.js : la concurrence asynchrone.
Dans la leçon précédente, nous avons présenté comment utiliser superagent et cheerio pour obtenir le contenu de la page d'accueil. Il suffit de lancer une requête http get. Mais cette fois, nous devons récupérer le premier commentaire de chaque sujet, ce qui nous oblige à lancer une demande de lien de chaque sujet et à utiliser cheerio pour récupérer le premier commentaire.
CNode compte actuellement 40 sujets par page, nous devons donc lancer 1 40 requêtes pour atteindre notre objectif dans cette leçon.
Nous avons lancé les 40 dernières requêtes simultanément :), et nous ne rencontrerons pas de multi-threading ni de verrouillage. Le modèle de concurrence de Node.js est différent du multi-threading, alors abandonnez ces concepts. Pour être plus précis, je n'aborderai pas les questions scientifiques telles que pourquoi asynchrone est asynchrone et pourquoi Node.js peut être monothread mais concurrent. Pour les étudiants intéressés par cet aspect, je recommande vivement "Nine Lights and One Deep Node.js" de @puling : http://book.douban.com/subject/25768396/.
Certains amis plus sophistiqués ont peut-être entendu parler de concepts tels que les promesses et les générateurs. Mais pour ma part, je ne peux parler que de rappels. La raison principale est que personnellement, je n’aime que les rappels.
Nous devons utiliser trois bibliothèques pour ce cours : superagent cheerio eventproxy(https://github.com/JacksonTian/eventproxy )
Vous pouvez réaliser vous-même les travaux d’échafaudage et nous rédigerons ensemble le programme étape par étape.
Le premier app.js devrait ressembler à ceci
var eventproxy = require('eventproxy'); var superagent = require('superagent'); var cheerio = require('cheerio'); // url 模块是 Node.js 标准库里面的 // http://nodejs.org/api/url.html var url = require('url'); var cnodeUrl = 'https://cnodejs.org/'; superagent.get(cnodeUrl) .end(function (err, res) { if (err) { return console.error(err); } var topicUrls = []; var $ = cheerio.load(res.text); // 获取首页所有的链接 $('#topic_list .topic_title').each(function (idx, element) { var $element = $(element); // $element.attr('href') 本来的样子是 /topic/542acd7d5d28233425538b04 // 我们用 url.resolve 来自动推断出完整 url,变成 // https://cnodejs.org/topic/542acd7d5d28233425538b04 的形式 // 具体请看 http://nodejs.org/api/url.html#url_url_resolve_from_to 的示例 var href = url.resolve(cnodeUrl, $element.attr('href')); topicUrls.push(href); }); console.log(topicUrls); });
Exécuter le nœud app.js
Le résultat est le suivant :
OK, maintenant nous avons les adresses de toutes les URL. Ensuite, nous explorons toutes ces adresses et nous avons terminé avec Node.js, c'est aussi simple que cela.
Avant d'explorer, nous devons encore introduire la bibliothèque eventproxy.
Les étudiants qui ont écrit de manière asynchrone en js doivent tous savoir que si vous souhaitez obtenir des données de deux ou trois adresses simultanément et de manière asynchrone, et après avoir obtenu les données, utilisez ces données ensemble, la manière conventionnelle d'écrire est d'en conserver une vous-même. comptoir.
Définissez d’abord un var count = 0, puis comptez chaque fois que l’analyse réussit. Si vous souhaitez capturer des données provenant de trois sources, puisque vous ne savez pas qui effectuera ces opérations asynchrones en premier, chaque fois que la capture réussit, vérifiez le nombre === 3. Lorsque la valeur est vraie, utilisez une autre fonction pour continuer l'opération.
Eventproxy joue le rôle de ce compteur. Il vous aide à gérer si ces opérations asynchrones sont terminées. Une fois terminées, il appellera automatiquement la fonction de traitement que vous avez fournie et transmettra les données capturées en paramètres.
En supposant que nous n'utilisons pas de proxy d'événement ou de compteurs, la manière de capturer trois sources est la suivante :
// Reportez-vous à la méthode $.get de jquery
$.get("http://data1_source", function (data1) { // something $.get("http://data2_source", function (data2) { // something $.get("http://data3_source", function (data3) { // something var html = fuck(data1, data2, data3); render(html); }); }); });
Tout le monde a écrit le code ci-dessus. Obtenez d'abord data1, puis data2, puis data3, puis baisez-les et sortez.
Mais tout le monde aurait dû penser qu'en fait, les données de ces trois sources peuvent être obtenues en parallèle. L'acquisition de data2 ne dépend pas de la complétion de data1, et de même, data3 ne dépend pas de data2.
On utilise donc un compteur pour l'écrire, et il s'écrira ainsi :
(function () { var count = 0; var result = {}; $.get('http://data1_source', function (data) { result.data1 = data; count++; handle(); }); $.get('http://data2_source', function (data) { result.data2 = data; count++; handle(); }); $.get('http://data3_source', function (data) { result.data3 = data; count++; handle(); }); function handle() { if (count === 3) { var html = fuck(result.data1, result.data2, result.data3); render(html); } } })();
Même si c'est moche, ce n'est pas si moche que ça. L'essentiel est que le code que j'écris soit beau.
Si on utilisait eventproxy, cela s'écrirait ainsi :
var ep = new eventproxy(); ep.all('data1_event', 'data2_event', 'data3_event', function (data1, data2, data3) { var html = fuck(data1, data2, data3); render(html); }); $.get('http://data1_source', function (data) { ep.emit('data1_event', data); }); $.get('http://data2_source', function (data) { ep.emit('data2_event', data); }); $.get('http://data3_source', function (data) { ep.emit('data3_event', data); });
C'est bien mieux, non ? C'est juste un compteur avancé.
ep.all('data1_event', 'data2_event', 'data3_event', function (data1, data2, data3) {});
这一句,监听了三个事件,分别是 data1_event, data2_event, data3_event,每次当一个源的数据抓取完成时,就通过 ep.emit() 来告诉 ep 自己,某某事件已经完成了。
当三个事件未同时完成时,ep.emit() 调用之后不会做任何事;当三个事件都完成的时候,就会调用末尾的那个回调函数,来对它们进行统一处理。
eventproxy 提供了不少其他场景所需的 API,但最最常用的用法就是以上的这种,即:
先 var ep = new eventproxy(); 得到一个 eventproxy 实例。
告诉它你要监听哪些事件,并给它一个回调函数。ep.all('event1', 'event2', function (result1, result2) {})。
在适当的时候 ep.emit('event_name', eventData)。
eventproxy 这套处理异步并发的思路,我一直觉得就像是汇编里面的 goto 语句一样,程序逻辑在代码中随处跳跃。本来代码已经执行到 100 行了,突然 80 行的那个回调函数又开始工作了。如果你异步逻辑复杂点的话,80 行的这个函数完成之后,又激活了 60 行的另外一个函数。并发和嵌套的问题虽然解决了,但老祖宗们消灭了几十年的 goto 语句又回来了。
至于这套思想糟糕不糟糕,我个人倒是觉得还是不糟糕,用熟了看起来蛮清晰的。不过 js 这门渣渣语言本来就乱嘛,什么变量提升(http://www.cnblogs.com/damonlan/archive/2012/07/01/2553425.html )啊,没有 main 函数啊,变量作用域啊,数据类型常常简单得只有数字、字符串、哈希、数组啊,这一系列的问题,都不是事儿。
编程语言美丑啥的,咱心中有佛就好。
回到正题,之前我们已经得到了一个长度为 40 的 topicUrls 数组,里面包含了每条主题的链接。那么意味着,我们接下来要发出 40 个并发请求。我们需要用到 eventproxy 的 #after API。
大家自行学习一下这个 API 吧:https://github.com/JacksonTian/eventproxy#%E9%87%8D%E5%A4%8D%E5%BC%82%E6%AD%A5%E5%8D%8F%E4%BD%9C
我代码就直接贴了哈。
// 得到 topicUrls 之后 // 得到一个 eventproxy 的实例 var ep = new eventproxy(); // 命令 ep 重复监听 topicUrls.length 次(在这里也就是 40 次) `topic_html` 事件再行动 ep.after('topic_html', topicUrls.length, function (topics) { // topics 是个数组,包含了 40 次 ep.emit('topic_html', pair) 中的那 40 个 pair // 开始行动 topics = topics.map(function (topicPair) { // 接下来都是 jquery 的用法了 var topicUrl = topicPair[0]; var topicHtml = topicPair[1]; var $ = cheerio.load(topicHtml); return ({ title: $('.topic_full_title').text().trim(), href: topicUrl, comment1: $('.reply_content').eq(0).text().trim(), }); }); console.log('final:'); console.log(topics); }); topicUrls.forEach(function (topicUrl) { superagent.get(topicUrl) .end(function (err, res) { console.log('fetch ' + topicUrl + ' successful'); ep.emit('topic_html', [topicUrl, res.text]); }); });
输出长这样:
完整的代码请查看 lesson4 目录下的 app.js 文件
总结
今天介绍的 eventproxy 模块是控制并发用的,有时我们需要同时发送 N 个 http 请求,然后利用得到的数据进行后期的处理工作,如何方便地判断数据已经全部并发获取得到,就可以用到该模块了。而模块不仅可以在服务端使用,也可以应用在客户端