在不使用多个 "then's" 的情况下链接 Promise

Chaining Promises without using multiple "then's"

我正在学习如何使用 Promises。我有以下功能,returns 第 "i" 个 xkcd 漫画标题作为承诺:

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);
  });

有没有更优雅的方法来做到这一点而无需链接那么多 "then"?假设我想获得前 50 个漫画标题,我将不得不链接很多 "then"s。

我可以在不使用 Promises 的情况下使用递归回调来做到这一点:

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 做同样的事情。谢谢。

您可以将 promise 推送到一个数组,然后 return Promise.all,当所有 promise 都已解决时,它会解决,例如

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 */ })

如果要按顺序获取文章:

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 => ...);

我不假装上面的所有内容在复制/粘贴后都能正常工作,这只是您如何执行此操作的一个想法。

有几种方法。您可以用您需要的所有值填充一个数组,然后使用 .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 一样工作,序列化请求,并最终 returns 收集结果。这个不使用绑定,但接受链中上一步的 "next value"。

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

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