如何使用promises实现顺序异步计算

时间:2014-11-20 20:14:32

标签: javascript promise q

我有这样一个页面:

<html>
  <body>
    <table>
      <thead>
        <tr>
          <th>Link</th><th>Description</th>
        </tr>
      </thead>
      <tbody>
        <tr>
          <td><a href="https://www.google.com">Google</a></td><td>Search engine</td>
        </tr>
        <tr>
          <td><a href="https://github.com">Github</a></td><td>Code management</td>
        </tr>
      </tbody>
    </table>
  </body>
</html>

我想解析表格的每一行,并按照每个链接(获取HTML页面标题)来创建一个像这样的网站数组:

[ { name: 'Google',
title: 'Google',
descr: 'Search engine' },
  { name: 'Github',
title: 'GitHub · Where software is built',
descr: 'Code management' } ]

我认为这是使用Promises和Q库开始学习的一个很好的例子,但我没有理解promises是如何工作的。 在我写的代码下面:

var request = require('request');
var cheerio = require('cheerio');
var Q = require('q');

var sites = [];

var loadPage = function(url){
  var deferred = Q.defer();

  request(url, function (error, response, html) {
if (!error && response.statusCode == 200) {
  var $ = cheerio.load(html);
  deferred.resolve($);
} else {
  deferred.reject(new Error(error));
}
  });
  return deferred.promise;
}

var parseRows = function($){
  var promises = [];

  $("tbody tr").each(function(){
  var $cells = $('td', this);

  var $firstC = $cells.eq(0);
  var name  = $firstC.text();
  var link  = $firstC.find('a').attr('href');
  var descr = $cells.eq(1).text();
  promises.push(Q.fcall(function () {
      var site = {name: name, descr: descr};
      loadPage(link).then(function($){
        var title = $("title").text(); 
        console.log(title);
        // here I don't know how to set the title
        // as obj's attribute
       });
       return site;
     }));
  });
  return Q.all(promises);
}

var displayTitles = function(res){
  for (var i = 0, len = res.length; i < len; i++) {

    var obj = res[i];
  }
  return Q.fcall(function () {
    return sites;
  });
}

loadPage('http://127.0.0.1/sample.html')
  .then(parseRows)
  .then(displayTitles)
  .done();

我对loadPage功能感到满意,但我仍然坚持使用parseRows,因为我无法将标题设置为&#34; site&#34;宾语。此外,displayTitles最初是为了处理获取页面标题所需的逻辑而开发的,但现在几乎没用了。

我如何修改上面的代码,以便以更干净和可读的方式将所需的数组作为输出?

2 个答案:

答案 0 :(得分:0)

我认为您的主要问题是Q.fcall即时解决,而不是pageLoad解决后。一点重组应该有所帮助:

var promises = [];

// ...

$("tbody tr").each(function(){

// ..

  promises.push(loadPage(link).then(function($){
    var site = {name: name, descr: descr};
    site.title = $("title").text(); 
    return site;
  }));

});
return Q.all(promises);

至于如何进一步压缩代码,你可以试试这个:

var parseRows = function ($) {
  return Q.all($("tbody tr").map(function () {
    var $cells = $('td', this);
    var $firstC = $cells.eq(0);
    var name = $firstC.text();
    var link = $firstC.find('a').attr('href');
    var descr = $cells.eq(1).text();

    return loadPage(link).then(function ($) {
      // are you sure there is a TITLE element? Did you perhaps mean .title?
      return {name: name, descr: descr, title: $("title").text()};
    });
  });
};

我不知道你想在displayTitles函数中实现什么,所以我无法帮助那里。但我很确定你不需要额外的Q.fcall包装器。根据{{​​1}}(据我记得),你应该能够简单地Promises来解决。此外,个人而言,我坚持使用原生Promises API,但最近所有浏览器都支持IE(http://caniuse.com/#search=promise),但似乎您仍在使用节点。

答案 1 :(得分:0)

在使用Q框架后,我决定遵循@lordvald建议,然后切换到原生的Promises API。下面是回答我问题的代码:

var request = require('request')
var cheerio = require('cheerio')

var loadPage = function(url) {
    var promise = new Promise(function(resolve, reject) {
        request(url, function(error, response, html) {
            if (!error && response.statusCode == 200) {
                resolve(cheerio.load(html))
            } else {
                reject(new Error(error))
            }
        })
    })
    return promise
}

var parseRows = function($) {
    return $('tbody tr').map(function() {
        var $cells = $('td', this)
        var firstC = $cells.eq(0)
        var url = firstC.eq(0).find("a").attr("href")
        return {
            name: firstC.text(),
            descr: $cells.eq(1).text(),
            url: url
        }
    }).get()
}

var loadSiteTitle = function(sites) {
    return Promise.all(sites.map(function(site) {
        return loadPage(site.url).then(function($) {
            site.title = $("title").text()
            delete site.url
            return site
        })
    }))
}

loadPage('http://127.0.0.1/sample.html')
    .then(parseRows)
    .then(loadSiteTitle)
    .then(function(sites) {
        console.log(sites)
    })
    .catch(function(e) {
        console.log('Unexpected error: ' + e.message)
        process.exit(1)
    })