我一直在研究实现轮询功能的方法,并在https://davidwalsh.name/javascript-polling上找到了一篇很棒的文章。现在,使用setTimeout而不是setInterval进行轮询可以使日志更加合理,尤其是对于我无法控制且已显示出不同的响应时间的API。
因此,我尝试在自己的代码中实现这样的解决方案,以挑战我对回调,promise和事件循环的理解。我遵循了该帖子中概述的指导,以避免出现任何反模式Is this a "Deferred Antipattern"?,并确保在.then()promise resolve before inner promise resolved之前进行promise解析,这就是我遇到的问题。我已经将一些代码放在一起以模拟这种情况,因此我可以突出显示这些问题。
我的假设情况是这样的: 我有一个对服务器的API调用,该服务器以一个userID响应。然后,我使用该用户ID向另一个数据库服务器发出请求,该服务器返回一组数据,该数据将执行一些可能花费几分钟的机器学习处理。
由于延迟,任务被放入任务队列中,一旦完成,它将使用isComplete: false
到isComplete: true
更新NoSql数据库条目。这意味着我们随后需要每n
秒对数据库进行一次轮询,直到获得指示isComplete: true
的响应,然后我们才停止轮询。我知道轮询api有很多解决方案,但我还没有看到涉及承诺,条件轮询以及不遵循先前链接文章中提到的一些反模式的解决方案。如果我错过了任何事情,而这是重复的话,我会提前致歉。
到目前为止,该过程由以下代码概述:
let state = false;
const userIdApi = () => {
return new Promise((res, rej) => {
console.log("userIdApi");
const userId = "uid123";
setTimeout(()=> res(userId), 2000)
})
}
const startProcessingTaskApi = userIdApi().then(result => {
return new Promise((res, rej) => {
console.log("startProcessingTaskApi");
const msg = "Task submitted";
setTimeout(()=> res(msg), 2000)
})
})
const pollDatabase = (userId) => {
return new Promise((res, rej) => {
console.log("Polling databse with " + userId)
setTimeout(()=> res(true), 2000)
})
}
Promise.all([userIdApi(), startProcessingTaskApi])
.then(([resultsuserIdApi, resultsStartProcessingTaskApi]) => {
const id = setTimeout(function poll(resultsuserIdApi){
console.log(resultsuserIdApi)
return pollDatabase(resultsuserIdApi)
.then(res=> {
state = res
if (state === true){
clearTimeout(id);
return;
}
setTimeout(poll, 2000, resultsuserIdApi);
})
},2000)
})
我有一个与此代码有关的问题,因为它无法按照我的需要进行轮询:
我在帖子How do I access previous promise results in a .then() chain?的可接受答案中看到,应该“破坏链条”以避免庞大的.then()语句链条。我遵循了指南,并且似乎可以解决问题(在添加轮询之前),但是,当我控制台注销每一行时,似乎userIdApi
被执行了两次;在startProcessingTaskApi
定义中使用它,然后在Promise.all
行中使用它。
这是已知事件吗?发生这种情况很有意义,我只是想知道为什么发送两个请求以执行相同的Promise还是可以的,或者是否有办法阻止第一个请求的发生并将函数执行限制为Promise.all
声明?
对于来自JavaScript的Java语言,我是一个相当陌生的人,因此,如果有人指出我可能会缺少一些知识,以便能够完成此看似简单的任务,那么将不胜感激。
答案 0 :(得分:1)
我认为您快到了,似乎您只是在为javascript的异步特性而苦苦挣扎。肯定会使用诺言,理解如何将诺言链接在一起是实现用例的关键。
我将首先实现一个包装setTimeout
的方法以简化操作。
function delay(millis) {
return new Promise((resolve) => setTimeout(resolve, millis));
}
然后,您可以使用delay
函数重新实现“ API”方法。
const userIdApi = () => {
return delay(2000).then(() => "uid123");
};
// Make userId an argument to this method (like pollDatabase) so we don't need to get it twice.
const startProcessingTaskApi = (userId) => {
return delay(2000).then(() => "Task submitted");
};
const pollDatabase = (userId) => {
return delay(2000).then(() => true);
};
只要不满足条件,您只需在链中链接另一个promise,即可继续轮询数据库。
function pollUntilComplete(userId) {
return pollDatabase(userId).then((result) => {
if (!result) {
// Result is not ready yet, chain another database polling promise.
return pollUntilComplete(userId);
}
});
}
然后,您可以将所有内容放在一起以实现用例。
userIdApi().then((userId) => {
// Add task processing to the promise chain.
return startProcessingTaskApi(userId).then(() => {
// Add database polling to the promise chain.
return pollUntilComplete(userId);
});
}).then(() => {
// Everything is done at this point.
console.log('done');
}).catch((err) => {
// An error occurred at some point in the promise chain.
console.error(err);
});
答案 1 :(得分:0)
如果您能够实际使用async
和await
关键字,这将变得更容易 。
使用与杰克的答案相同的delay
函数:
async function doItAll(userID) {
await startTaskProcessingApi(userID);
while (true) {
if (await pollDatabase(userID)) break;
}
}