如何从递归请求中返回promise,并在数据匹配条件时进行解析
How can I return a promise from a recursive request and resolve it when the data matches a condition?
我正在使用一个返回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
- 如何使用React JS中的循环,根据条件渲染或不渲染表数据
- 如何根据某些条件向可编辑的jquery数据表添加或删除按钮
- 如何在循环对象时有条件地使用数据
- JavaScript / jquery:如果选择了单选按钮并且数据属性符合条件,则添加类
- Mongodb,通过具有多个条件的_id快速查找数据
- jTable条件显示隐藏基于数据所有者的编辑和删除按钮
- d3.js继续条件加载数据
- 根据条件删除数据行
- 如何根据特定条件将数据插入表中
- 基于条件的数据渲染在胡子.js
- Angular JS - 具有动态数据的条件内联样式
- 如何在使用 ng-repeat 显示表数据时放置条件新行
- 当数据超过 5k 时,为动手提供条件格式的最佳方法
- 在 Foreach 循环中使用 IF 条件来比较数据绑定值
- jQuery 数据表根据条件更改列的值
- 如何在 angularjs 中的条件语句中显示数据
- 从json数组中迭代并输出条件数据
- Highcharts条件数据标签
- KnockoutJs中的条件数据绑定
- 带有余烬的把手模板中的条件数据属性