2

I have an object like so:

let myObject = {
'db1': [db1_file1Id,db1_file2Id,db_1file3Id],
'db2': [db2_file1Id, db2_file2Id]
...
}

I iterate through through this object and on each iteration: I connect to the database, retrieve the file, do some stuff and save the file back. Basically asynchronous stuff.

  for (let prop in myObject) {
    if (myObject.hasOwnProperty(prop)) {
      doStuff(prop, myObject[prop]);
    }
  }

Now the doStuff function makes sure I have a local scope so there is no inconsistencies. But still, the execution is not synchronous due to the asynchronous operations inside each loop. I basically need one db to be completely processed before moving on to the next. How do I fix this?

One approach that I thought of was to have recursive loop. But as per my understanding, this would require me to change my data structure extensively which is sub-optimal imo.

let arr; //containing my data

process(x) {
 if (x < arr.length){
   //process(x+1) is called inside doStuff after asynchronous operations are complete
   doStuff(arr[x]);
 }
}
Priyath Gregory
  • 927
  • 1
  • 11
  • 37
  • @leaf You can create a serial promise chain from an array using recursion or Array.prototype.reduce. I added as answer. – HMR Dec 09 '17 at 06:44
  • Do you want to process the databases in a specific order ? –  Dec 09 '17 at 08:02
  • @leaf the ordering does not matter – Priyath Gregory Dec 09 '17 at 08:08
  • If the order doesn't matter then why does it need to be serialized? Does the next db function need the output of the previous one? – HMR Dec 09 '17 at 08:17
  • @HRM its precautionary to prevent any sort of server overloading – Priyath Gregory Dec 09 '17 at 08:35
  • @HRM I have done an implementation based on responses. I also have a follow up question with an overall idea of what i'm trying to do. Would appreciate your input https://stackoverflow.com/q/47727435/4340953 – Priyath Gregory Dec 09 '17 at 10:16
  • You could do it parallel with Promise all but throttled (max active promises and or max promises started per time frame). Something like maximum 10 active or maximum 10 started per second or maximum 10 started per second and only maximum 10 active. https://github.com/amsterdamharu/lib/blob/master/src/index.js#L224-L274 – HMR Dec 09 '17 at 13:55

5 Answers5

2

You could use the solution you proposed at the end using Object.entries(obj). For example,

let arrProps = Object.entries(myObject);

process(index) {
 if (index < arrProps.length){
   // Call the callback once you complete execution of doStuff
   doStuff(arrProps[index], () => process(index + 1));
 }
}

Inside doStuff:

function doStuff(props, callback) {
    // Process props
    //finally in the promise of async call, on success call
    .then(callback)
}

OR you could use a generator function, if you want to use for ... in loop.

ayushgp
  • 4,891
  • 8
  • 40
  • 75
  • You could use recursion but no need to change doStruff, just have the process function apply recursion. If process has both key and array you can handle rejected promises (know what key rejected) and accumulate resolved values. I think the easier way is to reduce the array to a promise. Added both methods in my answer but without error handler (one rejects, everything after never gets processed) and resolve value is ignored. – HMR Dec 09 '17 at 06:11
2

The following will do what you ask, it returns an array of resolve values.

Do you want to stop processing if any one of them rejects? In case you need to make some changes, now it rejects if any of them reject and won't continue processing they keys in your object (object named myObject):

var myObject = {
  'one': ["one"],
  'two': ["two"]
};
var doStuff = arr =>
  console.log("starting:", arr[0]) ||
  Promise.resolve(arr[0]);

var [promise,result] = 
  Object.keys(myObject)
    .reduce(
      ([promise,results], key) =>
        [
          promise
          .then(
            resolve =>
              doStuff(myObject[key])
          )
          .then(
            resolve => results.push(resolve)&&resolve
          )
          .then(
            resolve => console.log("done:", resolve)
          )
          ,results
        ]
      , [Promise.resolve(), []]
    )
promise.then(
  _ => {
    console.log("done all",result)
  }
);

The answer ayushgp uses recursion, here is a working example that doesn't need changes to doSomething:

var myObject = {
  'one': ["one"],
  'two': ["two"]
};
var doStuff = arr =>
  console.log("starting:",arr[0]) ||
  Promise.resolve(arr[0])

var process = (arr,processFn) => {
  const rec = (arr,processFn,promise,results) =>
    arr.length === 0
      ? promise.then(_=>results)
      : promise
        .then(_ => processFn(arr[0][1]))
        .then(result=>results.push(result)&&console.log("resolved:",result))
        .then(_ => rec(arr.slice(1),processFn,promise,results));
  return rec(arr,processFn,Promise.resolve(),[]);
};
process(
  Object.keys(myObject).map(key=>[key,myObject[key]]),
  doStuff
)
.then(
  results => console.log("done all, results:",results)
);
HMR
  • 37,593
  • 24
  • 91
  • 160
1

One solution would be to make doStuff return a Promise which you can use to build a chain of promises using calls to then.

The Bluebird promise library provides this functionality with .each and .mapSeries.

You could implement it as:

Promise.forEachSeries = function(array, action) {
  return array.reduce(function(prevPromise, value, index, array) {
    return prevPromise.then(function() {
      return action(value, index, array);
    });
  }, Promise.resolve());
}

You would use it like this:

Promise.forEachSeries(arr, doStuff);
4castle
  • 32,613
  • 11
  • 69
  • 106
1

The following code might be close to what you are asking. I use indices i and j to loop through databases and files respectively :

var dbs = {
  db1: ["q", "w", "e", "r"],
  db2: ["t", "y"]
};

var names = Object.keys(dbs);
var db, x, i = 0, j = 0;

if (names.length > 0) {
  db = dbs[names[i]];
  x = db[j];
  console.log("start");
  asyncProcessing(x)
  .then(onSuccess)
  .catch(onFailure);
}

function onFailure (e) {
  console.log("[FAILURE]", e);
  console.log("end");
}

function onSuccess (xx) {
  console.log("[SUCCESS]", xx);
  j = (j + 1) % db.length; // next j
  if (j === 0) i = i + 1; // next i
  if (i < names.length) {
    db = dbs[names[i]];
    x = db[j];
    asyncProcessing(x)
    .then(onSuccess)
    .catch(onFailure);
  } else {
    console.log("end");
  }
}

function asyncProcessing (x) {
  return new Promise(function (resolve, reject) {
    setTimeout(function () {
      // force first two success then random
      if (x === "q" || x === "w" || Math.random() * 3 > 1) {
        resolve(x + x);
      } else {
        reject("Not lucky. Try again.");
      }
    }, 1000);
  });
}
0

The Promise object represents the eventual completion (or failure) of an asynchronous operation, and its resulting value. you can try like .

$("#myPara").delay(4500).fadeOut().promise().done(function(){
     $("#myHeading").attr("style","display:none;")  ;
      for(var i=10;i<15;i++){
      console.log(i);
      }
});
console.log("Hello promise !");
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<p id="myPara"> Hello </p>
<h1 id="myHeading">to be hide</h1>
  for (let prop in myObject) {
    if (myObject.hasOwnProperty(prop)) {
      var stuff= doStuff(prop, myObject[prop]).promise().done(function(){
      // Do whatever u want after completion of doStuff
     });
    }
   }

Have a look at Mozila ref.

manikant gautam
  • 3,521
  • 1
  • 17
  • 27