1

I currently have this isssue where I am calling an API in javascript that returns the status of a submitted job. The job status can be 'QUEUED', 'RUNNING' or 'COMPLETED'. I receive the results of this API call in the form of a promise. I have a function called pollJob that makes an API call and returns a promise that eventually resolves and lets me know whether the job is done or not. My problem is, I have to repeatedly call pollJob until I get a job status that resolves to 'COMPLETED' and then send out some sort of a notification to trigger some sort of action on the front end to occur as a result.

Here is an example of what I have been trying to do so far, but it does not work as Javascript has no notion of a delay function and each timeout will not block but rather fire off, let the code proceed, and then fire off the containing code once the allotted time has passed. I am using setTimeout and multiplying my delay each loop to make the call in staggered instances, basically spreading out each API call because job completion can take up to a minute:

  var res = "";
  var i = 0;
  var delay = 1000;
  while (!api.isJobDone(res) && i < 15) {
    setTimeout(function() {
      api.pollJob()
      .then(function(status) {
        console.log("STATUS IN LOOPUNTILDONE: " + status);  
        (res = status)
        if (api.isJobDone(status)) {
          // let the front end know the job is complete
        }
      });
    }, delay);
    delay *= 3;
    i++;
  }
  return res;

I'm having a lot of trouble thinking about this code in an asynchronous manner and am having a hard time thinking about how to call this promise within a loop until it gives me the desired result and then sending that information out to the front end to let that part of the code know to proceed. Is there a smarter way to design this? Any suggestions or tips are greatly appreciated. Thank you!

GenericAlias
  • 435
  • 2
  • 4
  • 19

2 Answers2

5

If you are using Node 8 you can use async/await which lets you write this in a very readable way.

// a delay function
function wait(delay){
    return new Promise(resolve => setTimeout(resolve, delay))
}

// poll in while loop until conditions are met
async function poll(){
    let res
    let i = 0
    const DELAY = 1000
    while (!api.isJobDone(res) && i < 15){
        res = await api.pollJob()
        console.log("STATUS IN LOOPUNTILDONE: " + res); 
        if (api.isJobDone(res)) {
            console.log("done")
        }
        await wait(DELAY)   
        i++     
    }
}
// make it so
poll()

Here's a snippet with a mocked out api that should run in a modern browser:

// mock api
var api = {
  pollJob: function() {
    return new Promise((resolve) => {
      setTimeout(() => resolve(Math.random()), 500)
    })
  },
  isJobDone: function(status) {
    return status < 0.3
  }
}

function wait(delay) {
  return new Promise(resolve => setTimeout(resolve, delay))
}

async function poll() {
  let res
  let i = 0
  const DELAY = 1000
  while (!api.isJobDone(res) && i < 15) {
    res = await api.pollJob()
    console.log("STATUS IN LOOPUNTILDONE: " + res);
    if (api.isJobDone(res)) {
      console.log("done")
    }
    await wait(DELAY)
    i++
  }
}
poll()
Mark
  • 90,562
  • 7
  • 108
  • 148
  • Thank you! I feel like I'm going to have to transition my code towards using async/await in the near future, so this definitely helps! – GenericAlias Nov 12 '17 at 17:57
  • 1
    Yes, @GenericAlias it makes some patterns (like while loops) feel like old-fashioned synchronous code. – Mark Nov 12 '17 at 17:58
1

This may be a better fit for recursion, instead of using a while loop:

function checkIfJobIsDone() {
    api.pollJob().then(function(status) {
        console.log("STATUS IN LOOPUNTILDONE: " + status); 
        res = status;
        if (api.isJobDone(status)) {
            // let the front end know the job is complete
        } else {
            checkIfJobIsDone();
        }
    });
}

This way, it will not make another API call until the previous one has returned

user184994
  • 17,791
  • 1
  • 46
  • 52