spirytus
spirytus

Reputation: 10946

Why my recursive Javascript function with Promises fails

I have JSON object that I need to clean up of properties starting with $. So given structure below it should get rid of $aaa, $bbb, $ccc and $eee:

{
  $aaa: "$aaa",
  bbb: "bbb",
  $ccc: {
    $ccc2: "$ccc2",
    ccc2a: "ccc2a"
  },
  ddd: {
    $ddd: "$ddd2",
    ddd2a: "ddd2a"
  },
  $eee: "$eee",
  fff: "fff"
}

I also wanted to make it run asynchronously and use Promises. I'm having trouble however getting this to work. It fails to clean $eee and am not sure where I am going wrong. Below is full code and plunker is here:

function clean$(obj1) {
  var obj = obj1;
  return new Promise(function(res, rej) {
    setTimeout(function() {
      for (var i in obj) {
        if (obj.hasOwnProperty(i)) {
          if (i.match(/^\$/)) {
            console.log("delete this " + i);
            delete obj[i];
          } else if (typeof obj[i] === "object") return clean$(obj[i]);
        }
      }
      res();
    }, 1000);
  })
}

sample = {
  $aaa: "$aaa",
  bbb: "bbb",
  $ccc: {
    $ccc2: "$ccc2",
    ccc2a: "ccc2a"
  },
  ddd: {
    $ddd: "$ddd2",
    ddd2a: "ddd2a"
  },
  $eee: "$eee",
  fff: "fff"
}

clean$(sample).then(function(res) {
  console.log("why it never gets here???");
})

Upvotes: 1

Views: 70

Answers (2)

Andrew Shepherd
Andrew Shepherd

Reputation: 45222

As @knolleary has already said, your internal timeout function is returning when you call $clean, rather than continuing.

At this point, your outer closure has already returned a promise. This promise is just waiting for something to call res, but this call never happens.

And the initial call is waiting for this promise to resolve so it can write the log message "why it never gets here???".

You must make sure that the resolve method is called in all circumstance. Also, something has to wait for the internal call to $clean to resolve.

Here is a working solution:

function clean$(obj1) {
  var obj = obj1;
  return new Promise(function(res, rej) {
    setTimeout(function() {
      var promisesToWaitFor = [];
      for (var i in obj) {
        if (obj.hasOwnProperty(i)) {
          if (i.match(/^\$/)) {
            console.log("delete this " + i);
            delete obj[i];
          } else if (typeof obj[i] === "object") {
            promisesToWaitFor.push(clean$(obj[i]));
          }
        }
      };
      Promise.all(promisesToWaitFor).then(res);
    }, 1000);
  })
}

Plunkr Fork...

Upvotes: 1

knolleary
knolleary

Reputation: 10117

The problem is this line:

} else if (typeof obj[i] === "object") return clean$(obj[i]);

As soon as it encouters an object to recurse into, to returns the result of that recursive call. However, because it returns at that point, it doesn't finish iterating over the elements at the current level.

In you example, the code recurses to clean ddd and returns the result of that, so doesn't continue on to clean $eee.

There are various approaches to fixing this. One would be to build up a list of the promises returned be recursive calls to clean, and only return once they have all resolved.

Upvotes: 3

Related Questions