Rumah >hujung hadapan web >tutorial js >Pengalaman praktikal Nodejs: modul eventproxy mengawal concurrency_node.js
Matlamat
Buat projek lesson4 dan tulis kod di dalamnya.
Titik masuk kod ialah app.js Apabila node app.js dipanggil, ia akan mengeluarkan tajuk, pautan dan bab semua topik pada CNode (https://cnodejs.org/) halaman utama komuniti. Komen, dalam format json.
Contoh output:
[ { "title": "【公告】发招聘帖的同学留意一下这里", "href": "http://cnodejs.org/topic/541ed2d05e28155f24676a12", "comment1": "呵呵呵呵" }, { "title": "发布一款 Sublime Text 下的 JavaScript 语法高亮插件", "href": "http://cnodejs.org/topic/54207e2efffeb6de3d61f68f", "comment1": "沙发!" } ]
Cabaran
Berdasarkan matlamat di atas, keluarkan pengarang komen1 dan nilai mata beliau dalam komuniti cnode.
Contoh:
[ { "title": "【公告】发招聘帖的同学留意一下这里", "href": "http://cnodejs.org/topic/541ed2d05e28155f24676a12", "comment1": "呵呵呵呵", "author1": "auser", "score1": 80 }, ... ]
Mata Pengetahuan
Alami keindahan neraka panggilan balik Node.js
Belajar menggunakan eventproxy, alat untuk mengawal konkurensi
Kandungan kursus
Dalam bab ini kita sampai ke bahagian Node.js yang paling hebat - serentak tak segerak.
Dalam pelajaran sebelumnya, kami memperkenalkan cara menggunakan superagent dan cheerio untuk mendapatkan kandungan halaman utama Ia hanya perlu memulakan permintaan http get. Tetapi kali ini, kami perlu mendapatkan ulasan pertama bagi setiap topik, yang memerlukan kami memulakan permintaan untuk pautan setiap topik dan menggunakan cheerio untuk mendapatkan ulasan pertama.
CNode pada masa ini mempunyai 40 topik setiap halaman, jadi kami perlu memulakan 1 40 permintaan untuk mencapai matlamat kami dalam pelajaran ini.
Kami memulakan 40 permintaan terakhir secara serentak :), dan kami tidak akan menghadapi berbilang benang atau mengunci Model serentak Node.js berbeza daripada berbilang benang, jadi tinggalkan konsep tersebut. Untuk lebih spesifik, saya tidak akan membincangkan isu saintifik seperti mengapa tak segerak adalah tak segerak dan mengapa Node.js boleh menjadi satu utas tetapi serentak. Bagi pelajar yang berminat dalam aspek ini, saya amat mengesyorkan "Sembilan Cahaya dan Satu Node Dalaman" @puling: http://book.douban.com/subject/25768396/.
Sesetengah rakan yang lebih canggih mungkin pernah mendengar tentang konsep seperti janji dan penjana. Tetapi bagi saya, saya hanya boleh bercakap tentang panggilan balik Sebab utama ialah saya secara peribadi hanya suka panggilan balik.
Kita perlu menggunakan tiga perpustakaan untuk kursus ini: superagent cheerio eventproxy(https://github.com/JacksonTian/eventproxy )
Anda boleh melakukan kerja perancah sendiri, dan kami akan menulis program bersama-sama langkah demi langkah.
Apl pertama.js sepatutnya kelihatan seperti ini
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); });
Jalankan nod app.js
Output adalah seperti yang ditunjukkan di bawah:
OK, kini kami telah mendapat alamat semua url Seterusnya, kami merangkak semua alamat ini dan kami sudah selesai.
Sebelum merangkak, kita masih perlu memperkenalkan perpustakaan eventproxy.
Pelajar yang telah menulis secara tidak segerak dalam js semua harus tahu bahawa jika anda ingin mendapatkan data daripada dua atau tiga alamat secara serentak dan tidak segerak, dan selepas memperoleh data, gunakan data ini bersama-sama, cara penulisan konvensional adalah dengan mengekalkannya sendiri kaunter.
Mula-mula tentukan kiraan var = 0, dan kemudian kira setiap kali rangkak berjaya. Jika anda ingin menangkap data daripada tiga sumber, kerana anda tidak tahu siapa yang akan menyelesaikan operasi tak segerak ini dahulu, maka setiap kali tangkapan berjaya, semak kiraan === 3. Apabila nilai adalah benar, gunakan fungsi lain untuk meneruskan operasi.
Eventproxy memainkan peranan kaunter ini Ia membantu anda mengurus sama ada operasi tak segerak ini selesai, ia akan secara automatik memanggil fungsi pemprosesan yang anda berikan dan menghantar data yang ditangkap sebagai parameter.
Dengan mengandaikan bahawa kami tidak menggunakan eventproxy atau kaunter, cara untuk menangkap tiga sumber adalah seperti berikut:
// Rujuk kaedah $.get 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); }); }); });
Semua orang telah menulis kod di atas. Mula-mula dapatkan data1, kemudian dapatkan data2, kemudian dapatkan data3, kemudian persetubuhan mereka dan keluarkan.
Tetapi semua orang sepatutnya berfikir bahawa sebenarnya, data daripada ketiga-tiga sumber ini boleh diperolehi secara selari.
Jadi kami menggunakan kaunter untuk menulisnya, dan ia akan ditulis seperti ini:
(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); } } })();
Walaupun ia hodoh, ia tidak begitu hodoh. Perkara utama ialah kod yang saya tulis kelihatan baik.
Jika kita menggunakan eventproxy, ia akan ditulis seperti ini:
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); });
Ia kelihatan lebih baik, bukan?
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 请求,然后利用得到的数据进行后期的处理工作,如何方便地判断数据已经全部并发获取得到,就可以用到该模块了。而模块不仅可以在服务端使用,也可以应用在客户端