如何在Node.js中完成所有Web抓取请求后才呈现页面?

我正在使用jsdom (Node.js的networking抓取库)在1到10个Web请求之间的任何地方。 它是这样的:

app.get('/results', function(req, res) { jsdom.env( "http://website1.com", ["http://code.jquery.com/jquery.js"], function (errors, window) { // scrape website #1 } ); jsdom.env( "http://website2.com", ["http://code.jquery.com/jquery.js"], function (errors, window) { // scrape website #2 } ); jsdom.env( "http://website3.com", ["http://code.jquery.com/jquery.js"], function (errors, window) { // scrape website #3 } ); } res.render('results', { items: items }); } 

在所有jsdom请求完成之后,我收集了所有我需要的信息之后,如何才运行res.render() ? 在同步世界中,这显然不会是一个问题,但由于JavaScript是asynchronous的,res.render()将在任何jsdomcallback完成之前运行。

天真的解决scheme

你可以用于less量刮擦的“天真”的解决scheme是嵌套一切(开始每个刮在最后一个刮的callback,最后一个callback包含渲染方法)。

 scrape cb: scrape cb: scrape cb: render all results 

当然,这变得单调乏味,难以辨认。 (所有东西都是串联的,不是平行的,不会很快)。

解决scheme更好

更好的解决scheme是编写一个函数来计算返回结果的数量,并在全部返回时调用render 。 这是一个实现:

 function parallel_cb(total, finalCallback) { var done = 0; var results = []; return function(result) { done += 1; results.push(result); if (total == done) finalCallback(results); } } 

在你的例子中使用它:

 app.get('/results', function(req, res) { var myCallback = parallel_cb( sitesToScrape.count, // or 3 in this case function(items) {res.render('results', { items: items })}); jsdom.env( "http://nodejs.org/dist/", ["http://code.jquery.com/jquery.js"], function (errors, window) { // do some scraping myCallback(result_from_scrape); } ); jsdom.env( "http://nodejs.org/dist/", ["http://code.jquery.com/jquery.js"], function (errors, window) { // more scraping myCallback(result_from_scrape); } ); jsdom.env( "http://nodejs.org/dist/", ["http://code.jquery.com/jquery.js"], function (errors, window) { // even more scraping myCallback(result_from_scrape); } ); }); 

最好的解决scheme

您应该真正学会使用现有的并行/asynchronous库,如@almypal在您的问题的评论中所build议的。

async你可以做一些更整洁的文档中描述的: https : //github.com/caolan/async#parallel

或者,如果所有的刮擦实际上在结果页面中查找相同的元素,甚至可以对URL数组进行平行映射以进行刮擦: https : //github.com/caolan/async#maparr-iterator-callback

每个scrape都可以使用async并行方法提供的callback函数来返回它的结果。 最后的[可选]callback将包含您的调用与所有项目render

编辑:你要求的例子

这是你的代码,直接翻译成async库:

 var async = require("async"); app.get('/results', function(req, res) { async.parallel( // the first argument is an array of functions [ // this cb (callback) is what you use to let the async // function know that you're done, and give it your result function (cb) { jsdom.env( "http://nodejs.org/dist/", ["http://code.jquery.com/jquery.js"], function (errors, window) { // do some scraping // async's callback expects an error for the first // param and the result as the second param cb(null, result_from_scrape); //No error } ); }, function (cb) { jsdom.env( "http://nodejs.org/dist/", ["http://code.jquery.com/jquery.js"], function (errors, window) { // more scraping cb(null, result_from_scrape); } ); }, function (cb) { jsdom.env( "http://nodejs.org/dist/", ["http://code.jquery.com/jquery.js"], function (errors, window) { // even more scraping cb(null, result_from_scrape); } ); } ], // This is the "optional callback". We need it to render. function (err, results) { // If any of the parallel calls returned an error instead // of null, it's now in the err variable. if (err) res.render('error_template', {error: err}); else res.render('results', { items: results }); }); });