链接承诺而不使用多个“那么”

时间:2017-09-07 03:59:55

标签: javascript node.js promise cheerio

我正在学习如何使用Promises。我有以下函数将“i”th xkcd漫画标题作为Promise返回:

var xkcd = function(i) {
  return new Promise(
    function(resolve, reject) {
      var tempurl = 'https://www.xkcd.com/' + i;
      request(tempurl, function(error, response, body) {
        if (error) reject(error);
        var $ = cheerio.load(body);
        resolve($('title').text() + '\n');
      });
    });
};

如果我想获得前4个标题,我就是这样链接我的.then():

var result = '';
xkcd(1)
  .then(fullfilled => {
    result += fullfilled;
  })
  .then(() => xkcd(2))
  .then(fullfilled => {
    result += fullfilled;
  })
  .then(() => xkcd(3))
  .then(fullfilled => {
    result += fullfilled;
  })
  .then(() => xkcd(4))
  .then(fullfilled => {
    result += fullfilled;
    console.log(result);
  });

有没有更优雅的方式来做这个而不用链接这么多“那么”?如果我想获得前50部漫画,我将不得不连结很多“当时”。

我可以使用递归回调而不使用Promise来实现:

function getXKCD(n) {
  var i = 1;
  (function getURL(i){
    var tempurl = 'https://www.xkcd.com/' + i;
    request(tempurl, function(error, response, body) {
      if (error) console.log('error: ' + error);
      var $ = cheerio.load(body);
      //prints the title of the xkcd comic
      console.log($('title').text() + '\n');
      i++;
      if (i <= n) getURL(i);
    });
  })(i);
}

getXKCD(4);

但我很想知道我是否可以对Promises做同样的事情。谢谢。

4 个答案:

答案 0 :(得分:4)

你可以将promises推送到一个数组,然后返回Promise.all,这将解决所有promises已经解决的问题,例如

function getXKCD(_start, _end) {
  if (_end >= _start) return Promise.reject('Not valid!');
  var promises = [];

  (function rec(i) {
    var p = new Promise(function(resolve, reject) {
      request('https://www.xkcd.com/' + i, function(error, response, body) {
        if (error !== null) return reject(error);

        if (i <= _end) rec(++i);
        let $ = cheerio.load(body);
        resolve($('title').text());
      });
    });

    promises.push(p);
  })(_start);

  return Promise.all(promises);
}

getXKCD(1, 50).then(res => { /* All done ! */ }).catch( err => { /* fail */ })

答案 1 :(得分:3)

如果您想按顺序获取文章:

function getSequentially(currentArticle, doUntil) {
  if (currentArticle !== doUntil) {
    xkcd(currentArticle)
      .then(article => getSequentially(currentArtile + 1, doUntil))
  }
}

如果您想立即获取所有文章:

Promise
  .all(new Array(AMOUNT_OF_ARTICLES).fill(null).map((nll, i) => xkcd(i + 1)))
  .then(allArticles => ...);

我不会假装上述所有内容在复制/粘贴后都能正常工作,这只是了解如何执行此操作。

答案 2 :(得分:1)

有几种方法。您可以使用所需的所有值填充数组,然后使用.map() Promise.all().reduce(),以便它们按顺序发生:

function getXkcd(count) {
  // Make an array of the comic numbers using ES6 Array.fill()...
  var ids = new Array(count).fill(1).map((val, index)=>index+1)

  // Now you can .map() or .reduce() over this list.
}

还有其他有趣的方法可以解决这个问题。您可以使用递归包装器来完成工作。保留原始xkcd函数,您可以构建一个递归调用自身的简单函数...

function getXkcd(max, last) {

    var current = last ? last + 1 : 1;

    xkcd(current)
    .then(function(title) {
        // Process the data.
        result += title;
        // We don't really care about a return value, here.
    })
    .then(getXkcd.bind(null, max, current))
    .catch(function(error) {
        // We should do something to let you know if stopped.
    });
}

这非常接近你的回调版本。唯一真正的区别是我们使用bind来传递当前值和最大值而不是闭包。这确实有一个好处,它从中间开始自动处理:getXkcd(50, 15);这也可以添加到你的回调示例中。

使用闭包让我们保持状态并创建一个可能更清洁的递归调用:

function getXKCD(max, start) {

    var result = "";

    var getNext = function(id){

        // If we are done, return the result
        if (id > n) {
            return result;
        }

        // Otherwise, keep going.
        return xkcd(id)
        .then(function(title){
            // Accumulate the title in our closure result
            result += title;
            // Send next value
            return id + 1;
        })
        .then(getNext);
    }

    // Kick off the loop
    return getNext(start || 1);
}

getXKCD(50).then(function(results){
    // Do something with the results
}, function(error){
    // Tell us what went wrong
});

getXKCD内,我们创建了一个函数getNext,它在Promise链的末尾调用自身。它的工作方式类似于reducer,序列化请求,并最终返回收集的结果。这个不使用bind,但接受链中上一步的“下一个值”。

答案 3 :(得分:0)

使用async-await是最好的方式,IMO:

   (async () => {
     const result = '';
     for(let i = 1; i < 50; i++) {
       result += await xkcd(i);
     }
     return result
   })().then(result => console.log(result))