如何从递归请求中返回promise并在数据与条件匹配时解析它?

时间:2016-07-27 15:14:23

标签: javascript jquery ajax recursion promise

我正在使用返回JSON的API,在此页面上我有一个进度条,指示了根据用户的请求设置内容的各个步骤。每个后续的AJAX请求的成功回调都会启动下一个请求,因为它们需要按顺序完成。一步发出服务器端后台作业,端点返回事务ID。

在此流程之外,有一个函数可以检查另一个端点以查看此事务是否完成。如果它“待定”,我需要在一小段延迟后重新发出请求。

我使用了递归函数:

function checkTransaction(trxid) {
    window.profileTrx[trxid] = 0;
    trxurl = 'https://example.com/transaction/'+trxid;
    $.getJSON(trxurl,function(result) {
        if(result.status === 'pending') {
            setTimeout(function () {
                checkTransaction(trxid);
            },3000);
        } else {
            window.profileTrx[trxid] = result;
        }
    });
}

我使用窗口的原因是我可以通过它在来自它的回调中的ID来访问事务 - 如果有的话,这是一个很好的用例。但它变得凌乱,我缺乏经验开始妨碍我。循环遍历window.profileTrx[trxid]似乎是双重工作,并且没有按预期运行,循环太快并且崩溃了页面。同样,.then()中的下一步承诺是我的想法,但我无法弄清楚如何。

我如何使用promises实现这一点,以便启动递归“事务检查”的回调函数只有在API返回对检查的非挂起响应后才会继续执行其余的执行?

我可以让我的头围绕递归并返回一个承诺,但不能同时回复。任何和所有的帮助都受到了广泛的赞赏。

3 个答案:

答案 0 :(得分:3)

当我首先考虑承诺时,我的头脑总是更清晰:

// wrap timeout in a promise
function wait(ms) {
    var deferred = $.Deferred();
    setTimeout(function() {
        deferred.resolve();
    }, ms);
    return deferred.promise();
}

// promise to get a trxid
function getTRX(trxid) {
    var trxurl = 'https://example.com/transaction/'+trxid;
    return $.getJSON(trxurl);
}

现在原来的功能似乎很简单......

function checkTransaction(trxid) {
    window.profileTrx[trxid] = trxid;
    return getTRX(trxid).then(function(result) {
        if (result.status === 'pending') {
            return wait(3000).then(function() {
                return checkTransaction(trioxid);
            });
        } else {
            window.profileTrx[trxid] = result;
            return result;
        }
    });
}

来电者将如下所示:

return checkTransaction('id0').then(function(result) {
    return checkTransaction('id1');
}).then(function(result) {
    return checkTransaction('id2');
}) // etc

请记住,如果checkTransaction在很长一段时间内保持待定状态,那么您将建立很长的承诺链。确保get以3000ms的非常小的倍数返回。

答案 1 :(得分:1)

“延迟”解决方案(不推荐)

由于您在问题中使用jQuery,我将首先提出一个使用基于$.Deferred()对象的jQuery承诺实现的解决方案。正如@Bergi所指出的,这是considered an antipattern

// for this demo, we will fake the fact that the result comes back positive
// after the third attempt.
var attempts = 0;

function checkTransaction(trxid) {
  var deferred = $.Deferred();
  var trxurl = 'http://echo.jsontest.com/status/pending?' + trxid;

  function poll() {
    console.log('polling...');
    // Just for the demo, we mock a different response after 2 attempts.
    if (attempts == 2) {
      trxurl = 'http://echo.jsontest.com/status/done?' + trxid;
    }
    
    $.getJSON(trxurl, function(result) {
      if (result.status === 'pending') {
        console.log('result:', result);
        setTimeout(poll, 3000);
      } else {
        deferred.resolve('final value!');
      }
    });

    // just for this demo
    attempts++;
  }

  poll();

  return deferred.promise();
}

checkTransaction(1).then(function(result) {
  console.log('done:', result)
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>

这应该有效(运行代码段来查看),但正如链接答案中所提到的,这种“延迟”模式存在问题,例如未报告错误情况。

问题是jQuery承诺(直到最近的版本 - 我没有检查过)have massive issues that prevent better patterns from being used

另一种方法是使用专用的promise库,它在then()函数上实现正确的链接,这样你就可以以更强大的方式编写函数并避免使用“延迟的”反模式:

承诺组合解决方案(更好)

对于真正的承诺组合,它避免完全使用“延迟”对象,我们可以使用更符合要求的承诺库,例如Bluebird。在下面的代码片段中,我使用的是Bluebird,它为我们提供了一个可以正常运行的Promise对象。

function checkTransaction(trxid) {
  var trxurl = 'http://echo.jsontest.com/status/pending?' + trxid;

  var attempts = 0;

  function poll() {
    if (attempts == 2) {
      trxurl = 'http://echo.jsontest.com/status/done?' + trxid;
    }
    attempts++;
    console.log('polling...');
    
    // wrap jQuery's .getJSON in a Bluebird promise so that we
    // can chain & compose .then() calls.
    return Promise.resolve($.getJSON(trxurl)
      .then(function(result) {
        console.log('result:', result);
        if (result.status === 'pending') {
          
          // Bluebird has a built-in promise-ready setTimeout 
          // equivalent: delay()
          return Promise.delay(3000).then(function() {
            return poll();
          });
        } else {
          return 'final value!'
        }
      }));
  }

  return poll();
}

checkTransaction(1).then(function(result) {
  console.log('done:', result);
})
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/bluebird/3.4.1/bluebird.min.js"></script>

答案 2 :(得分:0)

您可以从函数返回promise,并且当所有返回的promise都被解析时,父函数的.then将解析。

查看详细信息。

https://gist.github.com/Bamieh/67c9ca982b20cc33c9766d20739504c8