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]);
 }
}
7
  • @leaf You can create a serial promise chain from an array using recursion or Array.prototype.reduce. I added as answer. Commented Dec 9, 2017 at 6:44
  • Do you want to process the databases in a specific order ? Commented Dec 9, 2017 at 8:02
  • @leaf the ordering does not matter Commented Dec 9, 2017 at 8: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? Commented Dec 9, 2017 at 8:17
  • @HRM its precautionary to prevent any sort of server overloading Commented Dec 9, 2017 at 8:35

5 Answers 5

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.

Sign up to request clarification or add additional context in comments.

1 Comment

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

Comments

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

Comments

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

Comments

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.

2 Comments

OP is already doing this but this is not synchronous even now, the next doStuff call would be executed before this is completed.
He wants to stop the next iteration till doStuff is not finished

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.