合并异步动作,promise.then()和递归setTimeout,同时避免“延迟反模式”

时间:2019-10-28 15:55:24

标签: javascript ecmascript-6 promise es6-promise

我一直在研究实现轮询功能的方法,并在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: falseisComplete: 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语言,我是一个相当陌生的人,因此,如果有人指出我可能会缺少一些知识,以便能够完成此看似简单的任务,那么将不胜感激。

2 个答案:

答案 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)

如果您能够实际使用asyncawait关键字,这将变得更容易

使用与杰克的答案相同的delay函数:

async function doItAll(userID) {
    await startTaskProcessingApi(userID);
    while (true) {
        if (await pollDatabase(userID)) break;
    }
}