【问题标题】:Chaining Promises without using multiple "then's"在不使用多个“then”的情况下链接 Promise
【发布时间】:2017-09-07 03:59:55
【问题描述】:

我正在学习如何使用 Promise。我有以下函数将“i”个 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);
  });

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

我可以在不使用 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.all
  • 问题是您需要顺序接收文章还是一次性接收所有文章?
  • 另外,您不必将result += fullfilled; 放在它自己的.then() 中。您可以在同一 .then() 中调用下一个函数。用这种方式剪掉几乎一半。但是,如果这些不必连续运行,那么您可以一次运行它们并使用Promise.all() 收集所有结果。
  • @zzzzBov 是的,看起来 Promise.all 将能够做我想做的事,谢谢。
  • 顺便说一句,XKCD has a JSON API。不需要 Cheerio :-)

标签: javascript node.js promise cheerio


【解决方案1】:

您可以将 Promise 推送到一个数组,然后返回 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 */ })

【讨论】:

  • 谢谢,看来 Promise.all 正是我所需要的。
【解决方案2】:

如果要按顺序获取文章:

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

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

【讨论】:

  • 谢谢,我想我可以同时工作。递归函数是个好主意。
  • Protip:Array.from({length: AMOUNT_OF_ARTICLES}, (_, i) =&gt; …) 更短、更快、更丑
  • @smnbrrv,有水平链接的吗? stackoverflow.com/q/46762491/632951
【解决方案3】:

有几种方法。你可以用你需要的所有值填充一个数组,然后使用 .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 链的末尾调用它自己。它像减速器一样工作,对请求进行序列化,并最终返回收集到的结果。这个不使用绑定,但接受链中上一步的“下一个值”。

【讨论】:

    【解决方案4】:

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

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

    【讨论】:

      猜你喜欢
      • 2016-06-16
      • 1970-01-01
      • 2015-08-02
      • 2020-01-12
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-05-24
      • 1970-01-01
      相关资源
      最近更新 更多