【问题标题】:How to use promises to achieve sequential asynch computation如何使用 Promise 实现顺序异步计算
【发布时间】:2016-02-12 13:25:33
【问题描述】:

我有一个这样的页面:

<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 卡住了,因为我无法将标题设置为“站点”对象的属性。此外,displayTitles 最初是为了处理获取页面标题所需的逻辑而开发的,但现在几乎没用了。

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

【问题讨论】:

标签: javascript promise q


【解决方案1】:

我认为您的主要问题是 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 包装器。根据Promises(据我记得)你应该可以简单地做retrun sites 来解决。另外,就我个人而言,我会坚持使用原生 Promises API,除了 IE (http://caniuse.com/#search=promise) 之外的所有最新浏览器都支持它,但看起来你无论如何都在使用节点。

【讨论】:

    【解决方案2】:

    在玩了一点 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)
        })
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2014-11-04
      • 2018-07-26
      • 1970-01-01
      • 2020-09-13
      • 1970-01-01
      • 1970-01-01
      • 2020-04-03
      相关资源
      最近更新 更多