Q: JavaScript recursion returning a promise never resolving In JavaScript, I have an array of objects being some tasks to do. I iterate through this array with a for loop with await, calling a function doOneTask with returns a Promise.
That works pretty well as long as the code inside doOneTask works as expected. However, those things often fail. Trying again helps almost all the time. So, I'd like to implement a procedure for auto-retrying inside the JavaScript code.
My idea was a recursive function: In case of a failure, doOneTask call itself till the promise if finally resolved.
My code looks like this:
var tasks = [{label: 'task0'},{label: 'task1'},{label: 'task2'}];
async function mainFunction() {
    for(let k = 0; k < tasks.length; k++) {
        await doOneTask(tasks[k]);
        console.log("doOneTask done for index " + k);
    }
    console.log("End reached!");
}

function doOneTask(task) {        
    return new Promise(async function (resolve,reject) {    
       console.log("Starting with: " + task.label);            
       let checkIfDoeSomeStuffWorked = await doSomeAsyncStuff();           
       if(checkIfDoeSomeStuffWorked == false) {
           console.log(task.label + ": FAILED");
           return doOneTask(task);
       }
       else {
           console.log(task.label + ": SUCCESS");
           resolve(true);
       }
    }); 
}

function doSomeAsyncStuff() {
    return new Promise(function (resolve,reject) {        
        var myRandom = Math.random();        
        if(myRandom < 0.3) {
            resolve(true);
        }
        else {
            resolve(false);
        }        
    });
}

mainFunction();

(In real life, doSomeAsyncStuff is a backend call which often fails. The random() part is just for demonstration. In reality, I also limit the number of trials, before stopping the script.)
However, it doesn't work. In case of a failure, the script stops after having reached the SUCCESS console log. I never get back to the loop and the next items in the loop never get executed.

A: You have no use for the q library dependency. async functions always return a Promise, so you can simplify your code quite a bit -
async function doOneTask (task) {
  const result = await doSomeAsyncStuff()
  if (result === false) {
    console.log(`${task} failed`)
    return doOneTask(task)
  }
  else {
    console.log(`${task} passed`)
    return true
  }
}

Your fake function doSomeAsyncStuff can be cleaned up too -
async function doSomeAsyncStuff () {
  return Math.random() < 0.3
}

But let's add a fake delay of 1 second so that we can show things working 100% -
async function doSomeAsyncStuff () {
  return new Promise(resolve =>
    setTimeout(resolve, 1000, Math.random() < 0.3)
  )
}

Last, your main function uses a really old looping convention. As you're using modern JavaScript, you might as well use for-of syntax -
async function main (tasks = []) {
  for (const t of tasks) {
    await doOneTask(t)
  }
  return "done"
}

Finally we run the program -
const tasks =
  [ 'task0', 'task1', 'task2' ]

main(tasks).then(console.log, console.error)
// task0 failed
// task0 passed
// task1 failed
// task1 failed
// task1 passed
// task2 passed
// done

Expand the snippet below to verify the results in your own browser -


async function doOneTask (task) {
  const result = await doSomeAsyncStuff()
  if (result === false) {
    console.log(`${task} failed`)
    return doOneTask(task)
  }
  else {
    console.log(`${task} passed`)
    return true
  }
}

async function doSomeAsyncStuff () {
  return new Promise(resolve =>
    setTimeout(resolve, 1000, Math.random() < 0.3)
  )
}

async function main (tasks = []) {
  for (const t of tasks) {
    await doOneTask(t)
  }
  return "done"
}

const tasks =
  [ 'task0', 'task1', 'task2' ]

main(tasks).then(console.log, console.error)
// task0 failed
// task0 passed
// task1 failed
// task1 failed
// task1 passed
// task2 passed
// done



A: After having completed the question, but just before posting, something came into my mind: In the setup above, I don't resolve the very same promise object when I finally reach success, but for each function call a new promise object is generated. My solution/workaround is quite simple: use q the promise library passing the promise from one function call to the next function call:
var q = require('q');
async function doOneTask(task,promiseObj) {

    if(!promiseObj) {
        var promiseObj = q.defer();
    }

    console.log("Starting with: " + task.label); 

    let checkIfDoeSomeStuffWorked = await doSomeAsyncStuff();

    if(checkIfDoeSomeStuffWorked == false) {
           console.log(task.label + ": FAILED");
           return doOneTask(task,promiseObj);
    }
    else {
           console.log(task.label + ": SUCCESS");
           promiseObj.resolve(true);
       }

    return promiseObj.promise;
}

That way, we make sure that the very same promise object which is generated at the first call of doOneTask is resolved in the end - event after the 20th execution.
