'Polling until getting specific result?

I am currently trying to add polling to my application using this link https://davidwalsh.name/javascript-polling (and many others).

I have access to the following already implemented api:

client.get('url')
// returns Promise with result of getting result from url
// for the application I am working on,
//     the URL returns json that looks like the following 
//     {status: DONE or IN PROGRESS, other values...}
// when status is DONE other values are what I will use in the application

client.post('url', {data: passAnyDataHere}) 
// sends a post request with result of sending data to url
// starts the specific job

One of the problems that I have run into is that while trying to adapt the JavaScript Polling code I linked to above, when I find out that the status is DONE, I have no way of returning the result to outside of the Promise. Can someone give me tips on how to do this? (polling until I find a specific value, and then return the value for use later)

Let me give you an example

export default function someFunction() {
    let a = client.get('/status');
    a.then( dataResult => 
      {
         if (dataResult.status == "DONE") {
            //** want to get other values in dataResult here 
            // and store it somewhere else for use later
         }
      });
    // ***want to work with results here.
    // need some way to get the status of what happened inside the .then(..) part
   //  eventually have to return success or failure and results to the frontend
   // (this part is already done)
 }

The base of the code is https://github.com/erikras/react-redux-universal-hot-example#server-side-data-fetching (uses React.js/Node.js/Redux/etc.)

Any tips/suggestions/help are/is appreciated. Thanks!

Also, the application I am working on does not use JQuery.



Solution 1:[1]

Here's an more extensible solution based on the post polling with async/await

Just add the following utility methods:

const poll = async function (fn, fnCondition, ms) {
  let result = await fn();
  while (fnCondition(result)) {
    await wait(ms);
    result = await fn();
  }
  return result;
};

const wait = function (ms = 1000) {
  return new Promise(resolve => {
    setTimeout(resolve, ms);
  });
};

Then you can call it like this:

let fetchReport = () => axios.get(reportUrl);
let validate = result => !result.data.summary;
let response = await poll(fetchReport, validate, 3000);

Solution 2:[2]

Recent versions of Node.js, have support for async / await.

Below is an example of using it..

One major advantage of async / await, it's very easy to follow the code, and understand it's logic. If for example you wanted to extend this, to have a max try's feature, it would be trivial to do. (hint) It's just a for loop :)

let count = 0;

var client = {
  get: function () {
    return new Promise(function (resolve, reject) {
      count ++;
      setTimeout(function () {
        if (count > 4) resolve({status:'DONE',otherStuff:'Other Stuff'});
        else resolve({status: `count: ${count}`});
      }, 1000);
    });
  }
}


async function someFunction() {
  while (true) {
    let dataResult = await client.get('/status');
    console.log(dataResult.status);
    if (dataResult.status == "DONE") {
      return dataResult;
    }
  }
}

(async () => { let r = await someFunction(); console.log(r); })();

Solution 3:[3]

Here's an example of a function that uses promises and polls until you get the desired result. I've also parameterized it so that you can pass in the polling interval and a timeout value:

// create a promise that resolves after a short delay
function delay(t) {
    return new Promise(function(resolve) {
        setTimeout(resolve, t);
    });
}

// interval is how often to poll
// timeout is how long to poll waiting for a result (0 means try forever)
// url is the URL to request
function pollUntilDone(url, interval, timeout) {
    let start = Date.now();
    function run() {
        return client.get(url).then(function(dataResult) {
            if (dataResult.status === "DONE") {
                // we know we're done here, return from here whatever you 
                // want the final resolved value of the promise to be
                return dataResult;
            } else {
                if (timeout !== 0 && Date.now() - start > timeout) {
                    throw new Error("timeout error on pollUntilDone");
                } else {
                    // run again with a short delay
                    return delay(interval).then(run);
                }
            }
        });
    }
    return run();
}

// sample usage
// polls every 500ms for up to 30 seconds
pollUntilDone(someUrl, 500, 30 * 1000).then(function(result) {
   // have final result here 
}).catch(function(err) {
    // handle error here
});

The key here is to chain your promises so each time you call run() again, you return its value so it is chained to the prior promise. Then, whenever you finally return a value or throw an exception, the original promise will get that value or error as the resolved value or rejected reason.

Note that I added a timeout because you really never want to be polling forever, particularly in cases where some unforeseen and recurring error might not reject the promise, but won't get you the result you want.

Solution 4:[4]

I just had to solve a similar problem. Below is a gist of my solution:

// api call with interval until receiving a specific data.

const callApi = () => {
  return new Promise((resolve, reject) => {
    console.log('calledAPI!');
    setTimeout(()=>{
      var myNumber = parseInt(Math.random()*10, 10);
      resolve(myNumber);
    }, 1000);
  });
}

const callIntervalFunc = () => { // need to wrap it with a function because setInterval is not a function and cannot call it from outside otherwise.
  const callInverval = setInterval(async()=>{
    console.log('checking the api...');

    var responseNumber = await callApi();
    console.log('Got response! ',responseNumber);
    if (responseNumber === 5) {
      clearInterval(callInverval);
      console.log('responseNumber is 5!! ends.');
    }
  }, 2000);
}

callIntervalFunc();

Solution 5:[5]

Following continuousPromise method will do the job, it requires 2 parameters:

  1. Promise responsible for fetching data

  2. delay (in ms) in subsequent calls

    let exit = false;
    const continuousPromise = (promise, interval)  => {
        const execute = () => promise().finally(waitAndExecute);
        const waitAndExecute = () => {
            if (exit) {
                return;
            }
            setTimeout(execute, interval)
        };
        execute();
    }
    

How to use

continuousPromise(() => {
    return axios.get("something.json")
        .then((res) => {
            if (res && res.status !== 'PENDING') { // Check here for expected result
                exit = true;
            }
        })
        .catch((error) => {
            exit = true;
            console.log(error);
        })
}, 1000);

Solution 6:[6]

One option is to alter the poll function to only resolve when your required conditions have been met:

function poll(pollFn, interval = 100) {
    var intervalHandle = null

    return {
        until(conditionFn) {
            return new Promise((resolve, reject) => {
                intervalHandle = setInterval(() => {
                    pollFn().then((data) => {
                        let passesCondition = false;
                        try {
                            passesCondition = conditionFn(data);
                        } catch(e) {
                            reject(e);
                        }
                        if (passesCondition) {
                            resolve(data);
                            clearInterval(intervalHandle);
                        }
                    }).catch(reject)
                }, interval)
            })
        }
    }
}

var counter = 0;

function getStatus() {
    if (counter++ === 5) {
       return Promise.resolve({ status: 'DONE', otherStuff: 'hi' });
    }
    console.log('not DONE, keep going')
    return Promise.resolve({ status: 'WORKING' });
}

poll(getStatus, 500)
  .until(data => data.status === 'DONE')
  .then((data) => {
    // do something with the data
    console.log('status is DONE', data)
  })

Solution 7:[7]

Here's a simple alternative

(function poll(){

    //do the check - ajax, etc.

    if (condition_that_means_were_done) {
        return
    }   
        
    setTimeout(poll,timeout_ms)
})();

Solution 8:[8]

In someFunction(), it returning a new Promise which issued a resolve() and pass the processed result as parameter. In getpolledresult(), catch the processed result to determine whether to poll or not.

function getSearchResults(term) {
  return new Promise(resolve => {
    let timeout = 100 + Math.floor(Math.random() * 1900);
    console.log("is number < 500?", timeout);
    let result = {
      status: "",
      term_lower: term.toLowerCase(),
      term_upper: term.toUpperCase()
    };
    if (timeout < 500) {
      result.status = "DONE"
    }
    setTimeout(() => resolve(result), timeout);
  });
}

let cancelCallback = () => {};
let cancelflag = 0;

var sleep = (period) => {
  return new Promise((resolve) => {
    cancelCallback = () => {
      console.log("timeout...");
      // send cancel message...
      cancelflag = 1;
      return resolve('Canceled');
    }
    setTimeout(() => {
      resolve("tick");
    }, period)
  })
}

let asleep = async (period) => {
  let respond = await sleep(period);
  // if you need to do something as soon as sleep finished
  /* console.log("sleep just finished, do something...") */
  return respond;
}


function someFunction() {
  return new Promise((resolve) => {
    console.log("polling...");
    /* let a = client.get('/status'); */
    let a = getSearchResults('a');
    let processedResult = {
      status: ""
    };
    a.then(dataResult => {
      if (dataResult.status == "DONE") {
        //** want to get other values in dataResult here
        // and store it somewhere else for use later
        processedResult.status = "OK";
      };
      resolve(processedResult);
    });
  });
}


var getpolledresult = (promiseFn, period, timeout) => promiseFn().then((result) => {
  // ***want to work with results here.
  // need some way to get the status of what happened inside the .then(..) part
  //  eventually have to return success or failure and results to the frontend
  // (this part is already done)
  console.log(result);

  if (result.status !== "OK") {
    asleep(period).then((respond) => {
      // check if sleep canceled, if not, continue to poll
      if (cancelflag !== 1) {
        poll(promiseFn, period, timeout);
      }
    })
  }

});


var poll = (promiseFn, period, timeout) => {
  // just check if cancelCallback is empty function, 
  // if yes, set a time out to run cancelCallback()
  if (cancelCallback.toString() === "() => {}") {
    console.log("set timout...")
    setTimeout(() => {
      cancelCallback()
    }, timeout);
  }

  getpolledresult(promiseFn, period, timeout);
}


poll(someFunction, 1000, 10000);

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 KyleMit
Solution 2 Keith
Solution 3 jfriend00
Solution 4
Solution 5 Anshuman Jaiswal
Solution 6 Rob M.
Solution 7 Yehosef
Solution 8 maxshuty