0

I am trying to create an asynchronous polling function that checks the database periodically to see if a value exists. However, I want to give the user the ability to cancel this request. What is the proper way to trigger an asynchronous process to exit early? I have tried exposing the reject function outside of the polling function, but was not able to get it to work. Currently I am setting a boolean variable called cancelRequest in the function's outer scope and then trying to check if that value has been set to true via the method shouldCancel. That method is triggered when a user clicks a cancel button. If shouldCancel resolves to true, reject and break out early. This works, but not all the time. My guess is that the async polling process is continuing to execute before the other method updates the cancelRequest variable.

With that being said, does anyone have any tips or ideas of how I can implement a cancellable polling function that runs an asynchronous task? I've included my code below.

Thank you all very much in advance!

exports.pollForPortfolio = async (porfolioId) => {
    return poll({
    fn: () => this.getPortfolio(porfolioId),
    validate: validatePortfolioRequest,
    interval: 5000,
    maxAttempts: 50
  })
    .then(portfolio => {
        return portfolio
    })
    .catch(err => console.error(err));
}

const poll = async ({ fn, validate, interval, maxAttempts }) => {
    let attempts = 0;

    const executePoll = async (resolve, reject) => {
        const result = await fn();
        attempts++;
        console.log("poll result attempt #", attempts + ": " + JSON.stringify(result));
        if (validate(result.finished)) {
            return resolve(result);
        } else if (shouldCancel()) {
            cancelRequest = false;
            return reject(new Error('Request cancelled'));
        } else if (maxAttempts && attempts === maxAttempts) {
            return reject(new Error('Exceeded max attempts'));
        } else {
            setTimeout(executePoll, interval, resolve, reject);
        }
    };

    return new Promise(executePoll);
};

exports.getUserPortfolio = function(portfolioId) {
    return new Promise(function (resolve, reject) {
         // query server here to see if value is available
         request(options, function (error, response, data) {})
    }
}
samiam0906
  • 21
  • 1
  • 3
  • 1
    Avoid the [`Promise` constructor antipattern](https://stackoverflow.com/q/23803743/1048572?What-is-the-promise-construction-antipattern-and-how-to-avoid-it)! Your code breaks when `fn()` rejects/throws. – Bergi May 29 '20 at 19:47
  • 1
    Passing a "cancellation token" and repeatedly checking it is a fine approach. You might want to do the `shouldCancel()` before the `await fn()` though, not afterwards - you care about getting cancelled during the timeout. You might even use `clearTimeout()` from `cancelRequest`. – Bergi May 29 '20 at 19:49
  • @Bergi thanks for answering. could you expand a little on what you mean by the code breaks when `fn()` rejects/throws? – samiam0906 May 29 '20 at 21:21
  • 1
    It just doesn't work. You'll get an unhandled promise rejection, and the `poll()` result promise will never settle. With `async`/`await`, you can write the whole thing as a simple `while (!shouldCancel())` loop :-) – Bergi May 29 '20 at 21:58

0 Answers0