Polling until getting specific result?

Viewed 18103

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.

8 Answers

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);

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();

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);

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);

Here's a simple alternative

(function poll(){

    //do the check - ajax, etc.

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