目标
创建一个lesson4项目,并在其中编写代码。
代码的入口是app.js当节点app.js被调用时,它会以json格式输出CNode(https://cnodejs.org/)社区主页上所有主题的标题、链接和第一条评论。
输出示例:
[{ ' title ' : '[公告]发布招聘岗位的同学请注意这里',' href ' : ' http://cnodjs.org/topic/541ed2d05e28155f24676a12','评论1' : '呵呵呵' }。{'title': '发布一个JavaScript语法突出显示插件在崇高文本下',' href ' : ' http://cnodejs.org/topic/54207e2efffeb6de3d61f68f','评论1' :' sofa!'}]挑战。
在上述目标的基础上,输出comment1的作者及其在cnode社区中的积分值。
示例:
[{ ' title ' : '[公告]发布招聘岗位的同学要注意这个地方',' href ' : ' http://cnodjs.org/topic/541ed2d05e28155f24676a12','评论1' : '呵呵呵',' author1。
体验Node.js回调地狱之美
使用eventproxy控制并发性。
课程内容
在这一章中,我们来到了Node.js最牛逼的部分,——异步并发内容。
在上一课中,我们介绍了如何使用superagent和cheerio来获取主页内容,这只能通过发起http get请求来完成。但是这一次,我们需要拿出每个话题的第一条评论,这就需要我们发起一个请求,请求每个话题的链接,用cheerio拿出第一条评论。
CNode目前每页有40个主题,因此我们需要发起1 40个请求来实现本课的目标。
在后者的40个请求中,我们并发启动了它们:),并且我们不会遇到多线程和锁定。Node.js的并发模型不同于多线程,所以我们抛弃了那些想法。更具体地说,比如为什么异步毕竟是异步的,为什么Node.js可以在一个线程中并发,我就不讨论这些科学问题了。对这个领域感兴趣的同学,我强烈推荐@朴玲的《九浅一深Node.js》:
一些比较激进的朋友可能听说过承诺和发电机这样的概念。但是,我只讲回调,主要是因为我个人只喜欢回调。
在本课程中,我们需要使用三个库:super agent chef event proxy(https://github.com/jacksonian/event proxy)。你应该手脚并用。让我们一步一步一起写这个程序。
首先,app.js应该是这样的。
var event proxy=require(' event proxy ');var super agent=require(' super agent ');var cherio=require(' cherio ');//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 topiculps=[];var $=cheerio . load(RES . text);//获取所有链接$ ('# topic _ list.topic _ title ')。每个(function (idx,element){ var $ element=$(element);//$element.attr('href ')最初是/topic/542 accd 7 D5 d 28233425538 b 04//我们使用url.resolve来自动推断完整的url。转换成//https://cnodejs . org/topic/542 accd 7 d5d 28233425538 b04//的形式,请看http://nodejs.org/api/url.html#url_url_resolve_from_to var href=URL . resolve(cnodeurl,$ element.attr ('href '))的例子;topiculps . push(href);});console . log(topiculps);});运行nodeapp.js。
输出如下:
好的,现在,我们已经得到了所有的网址。接下来,我们对所有这些地址爬行一次,我们就完成了。Node.js就这么简单。在爬行之前,我们仍然需要引入library eventproxy。
任何用js写过异步的人都应该知道,如果想异步获取两三个地址的数据,并在获取数据后一起使用这些数据,常规的写法是自己维护一个计数器。
首先,定义一个var count=0,然后在每次成功抓取后计数。如果要从三个来源抓取数据,因为不知道谁会先完成这些异步操作,那么每次抓取成功,判断count===3。当值为真时,使用另一个函数继续完成操作。Eventproxy扮演这个计数器的角色,帮助您管理这些异步操作是否完成。完成后,它会自动调用您提供的处理函数,并将捕获的数据作为参数发送。假设我们不使用eventproxy或counter,获取三个源的方法如下:
//参考$。离开jquery。
$.get('http://data1_source ',function (data1) { //某物$。get('http://data2_source ',function (data2) { //某物$。get('http://data3_source ',function (data3) { //某物var html=hook(data 1,data2,data 3);渲染(html);});});});上面的代码大家都写过。先获取data1,获取后获取data2,然后获取data3,然后操他们,输出他们。
但是,我们应该想到,这三个来源的数据是可以并行获取的,data2的获取不依赖于data1的完成,data3也不依赖于data2的完成。
所以我们用计数器来写,它会这样写:
(function(){ var count=0;var结果={ };$.get('http://data1_source ',函数(data){ result . data 1=data;计数;handle();});$.get('http://data2_source ',函数(data){ result . data 2=data;计数;handle();});$.get('http://data3_source ',函数(data){ result . data 3=data;计数;handle();});函数句柄(){ if(count===3){ var html=hook(result . data 1,result.data2,result . data 3);渲染(html);} }})();丑,不丑,主要是因为我写的代码好看。
如果我们使用eventproxy,它会这样写:
var EP=new event proxy();ep.all('data1_event ',' data2_event ',' data3_event ',函数(data1,data2,data 3){ var html=hook(data 1,data2,data 3);渲染(html);});$.get('http://data1_source ',函数(数据){ ep.emit('data1_event ',数据);});$.get('http://data2_source ',函数(数据){ ep.emit('data2_event ',数据);});$.get('http://data3_source ',函数(数据){ ep.emit('data3_event ',数据);});好多了,不是吗?只是高级计数器。
ep.all('data1_event ',' data2_event ',' data3_event ',函数(data1,data2,data 3){ });在这句话中,我听了三个事件,分别是data1 _ event、data2 _ event和data3 _ event。每次当一个源的数据捕获完成后,我都会通过ep.emit()告诉ep某个事件已经完成。
当三个事件没有同时完成时,调用ep.emit()后将不会执行任何操作。当这三个事件都完成后,将调用末尾的回调函数来统一处理它们。
Eventproxy提供了许多其他场景所需的API,但最常用的用法是上面的一种,即:
var EP=new event proxy()first;获取eventproxy实例。
告诉它你想听哪些事件,并给它一个回调函数。ep.all('event1 ',' event2 ',函数(result1,result2) {}).在适当的时候ep.emit('event_name ',eventData)。
Eventproxy,这是处理异步并发的思想,我一直认为它就像程序集中的goto语句,程序逻辑在代码中处处跳跃。本来代码已经执行到100行了,突然80行的回调函数又开始工作了。如果你的异步逻辑比较复杂,这个80行的函数完成后,激活另一个60行的函数。虽然并发性和嵌套的问题已经解决,但是我们的祖先已经消除了几十年的goto语句又回来了。
至于这一套思想到底好不好,我个人觉得还是不错的,煮了之后看起来还是挺清楚的。然而,js是一种混乱的语言。推广了哪些变量(http://www . cn blogs.com/Damon LAN/archive/2012/07/01/2553425 . html)?没有主函数、变量的范围,数据类型通常像数字、字符串、散列和数组一样简单。编程语言的美丑,但愿我们心中有佛。
回到主题,我们已经得到了一个长度为40的topicUrls数组,其中包含每个主题的链接。这意味着接下来我们将发出40个并发请求。我们需要使用eventproxy的#after API。
让我们自己学习这个API:https://github.com/JacksonTian/eventproxy#重复异步协作,我的代码直接发布。
//在//获取topicUrls之后获取一个eventproxy实例var EP=new event proxy();//命令ep反复听topicUrls.length次(这里是40次)` topic_html `事件然后作用于EP.after ('topic _ html ',topicUrls.length,function (topics) {//topics是一个数组。包括ep.emit('topic_html ',pair)中的40对,共40次。//开始动作Topics=topics.map(函数(主题对){//接下来是jquery的用法。var Topicurl=TopicpAir[0];var topicHtml=topiccair[1];var $=cheerio . load(topicHtml);return ({ title: $(')。topic _ full _ title’)。文本()。trim(),href: topicUrl,comment1: $('。reply _ content’)。eq(0)。文本()。trim(),});});console . log(' final : ');console.log(主题);});topicUrls.forEach(函数(topiculp){ super agent . get(topiculp))。end(function (err,RES){ console . log(' fetch ' Topicurl ' success ');ep.emit('topic_html ',[topicur,RES . text]);});});输出如下所示:
有关完整的代码,请查看第4课目录中的app.js文件。
摘要
今天介绍的eventproxy模块用于控制和并发使用。有时我们需要同时发送n个http请求,然后利用获得的数据进行后处理工作。如何方便的判断所有的数据已经并发获取,我们可以使用这个模块。该模块不仅可以在服务器端使用,也可以在客户端使用。