如何从递归请求中返回promise,并在数据匹配条件时进行解析

How can I return a promise from a recursive request and resolve it when the data matches a condition?

本文关键字:条件 数据 递归 请求 promise 返回      更新时间:2023-09-26

我正在使用一个返回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;
        }
    });
}

我使用window的原因是,我可以通过它来自的回调中的ID来访问事务——如果有承诺的话,这是一个很好的用例。但事情变得一团糟,我缺乏经验开始妨碍我。在window.profileTrx[trxid]的状态上循环似乎是双重工作,并且没有像预期的那样运行,循环太快,导致页面崩溃。同样,在.then()中承诺下一步是我的想法,但我不知道怎么做。

我如何用promise来实现这一点,使得启动递归"事务检查"的回调函数只有在API返回对该检查的非挂起响应后,才会继续执行其其余部分?

我可以回过头来重复,并回报一个承诺,但不能同时做到这两件事。非常感谢所有的帮助。

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

// 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的某个非常小的倍数内返回。

基于"延迟"的解决方案(不推荐)

由于您在问题中使用的是jQuery,我将首先介绍一个使用jQuery基于$.Deferred()对象的promise实现的解决方案。正如@Bergi所指出的,这被认为是一个反模式。

// 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 promise(可能直到最近的版本——我还没有检查)存在大量问题,无法使用更好的模式。

另一种方法是使用专用的promise库,它在then()函数上实现了正确的链接,这样您就可以以更健壮的方式组合函数,并避免"延迟"的反模式:

Promise成分解决方案(更好)

对于真正的promise组合,它完全避免了使用"延迟"对象,我们可以使用更兼容的promise库,如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>

您可以从函数返回promise,当所有返回的promise都得到解析时,父函数的.then就会得到解析。

查看此以了解详细信息。

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