0

I need to be able to loop through an object of images and perform an asynchronous function on each image one at a time. I have it kind of working if I convert the images object to an array but I want to do this with a for...in loop so I can use the image keys as well. I also need to be able to perform an action at the end as I am currently.

var images = {
  ABD: '1464685684713583388647.jpg',
  ABY: '1457524543088191607099.jpg',
  ADV: '1478877365443818880647.jpg',
  AFD: '1457527861824290195088.jpg',
}
var imagesArray = Object.values(images);
var len = imagesArray.length;

function asynchronousImageFunction (key, image, onSuccess, onFail) {
  setTimeout(function () {
    console.log(key);
    console.log(image);
    onSuccess();
  }, Math.random() * 1000)
}

(function loop(i) {
  if (i < len) {
    new Promise(function (resolve, reject) {
      asynchronousImageFunction ('key', imagesArray[i], resolve, reject);
    }).then(loop.bind(null, i+1));
  } else {
    console.log('end');
  }
})(0);

The order isn't important but having them call one after the other is, and having an onComplete or end call is also needed. I just can't get my head round it, can anyone help?

2
  • Are you doing this in a browser, or node? Commented Apr 3, 2018 at 18:18
  • In a Cordova app ... so essential a browser. Commented Apr 3, 2018 at 18:19

3 Answers 3

2

Using reduce is a nice way to do this. You can pass the key/value pairs in with Object.entries

var images = {
  ABD: '1464685684713583388647.jpg',
  ABY: '1457524543088191607099.jpg',
  ADV: '1478877365443818880647.jpg',
  AFD: '1457527861824290195088.jpg',
}

function asynchronousImageFunction(key, image, onSuccess, onFail) {
  setTimeout(function() {
    console.log(key);
    console.log(image);
    onSuccess();
  }, 1000)
}

Object.entries(images).reduce((a, [key, value]) => {
  return a.then(() => new Promise((resolve, reject) => {
    asynchronousImageFunction(key, value, resolve, reject);
  }))}, Promise.resolve())
.then(() => console.log("end"))

If, on the other hand, your async function returned its own promise, this would be a little easier on the eyes:

var images = {
  ABD: '1464685684713583388647.jpg',
  ABY: '1457524543088191607099.jpg',
  ADV: '1478877365443818880647.jpg',
  AFD: '1457527861824290195088.jpg',
}

function asynchronousImageFunction(key, image, onSuccess, onFail) {
  return new Promise((resolve, reject) => {
    setTimeout(function() {
      console.log(key);
      console.log(image);
      resolve();
    }, 1000)
  })
}

Object.entries(images).reduce((a, [key, value]) => 
  a.then(() => asynchronousImageFunction(key, value))
  , Promise.resolve())
  .then(() => console.log("end"))

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

3 Comments

@juliusbangert I just added that (sorry I didn't notice in your OP). You can just tack a then() on it since it returns the last promise.
Eventually I will need to be able to support ES5 with polyfills, what would be the best way to do this considering the use of Object.entries ( I realise Promise will need pollyfill )
@juliusbangert this polyfil for Object.entries is good and simple to use: developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…
0

Just use Object.keys or Object.entries instead of Object.values if you also need to access the keys.

var imageKeysArray = Object.key(images);
var len = imagesArray.length;
(function loop(i) {
  if (i < len) {
    var key = imageKeysArray[i];
    var value = images[key];
    asynchronousImageFunction(key, value).then(loop.bind(null, i+1));
  } else {
    console.log('end');
  }
})(0);

Notice that the new Promise wrapper should be directly around the setTimeout call, inside asynchronousImageFunction; that makes it easier to use and you need to pass less callbacks around.

The alternative that would let you use a real for … in loop is async/await syntax:

(async function loop() {
  for (const key in images)
    await asynchronousImageFunction(key, images[key]);
  console.log("end");
})();

2 Comments

What are the disadvantages of using async? I had it in my mind that I needed to use Promise.
@juliusbangert No disadvantages (maybe apart from compatibility with old engines, but transpilers solve that). And you still would be using promises, asynchronousImageFunction still needs to return one in the snippet with await.
0

You aren't going to be able to do this with a for...in, really. Loops like for...in and for...of can't be instructed to wait for asynchronous events before beginning the next iteration.

What you have to do is implement something that behaves like the desired loop, but does wait for async events. What you've done behaves like a for...of over an array. You can make the keys available by doing something like what Mark_M described.

This is, however, a very common operation, and has been abstracted (along with many other asynchronous operations) in libraries like async, enabling you to skip this annoyance and just write what you want:

var images = {
  ABD: '1464685684713583388647.jpg',
  ABY: '1457524543088191607099.jpg',
  ADV: '1478877365443818880647.jpg',
  AFD: '1457527861824290195088.jpg',
}

function asynchronousImageFunction(key, image, callback) {
  setTimeout(function () {
    console.log(key);
    console.log(image);
    // Usual convention of `async` callbacks. The first argument should be
    // null/undefined/omitted if no error occurred:
    callback();
    // If there was an error, you would instead do this:
    // callback(err);
  }, Math.random() * 1000)
}

async.eachOfSeries(images, function(image, key, callback) {
    asynchronousImageFunction(key, image, callback);
}, function(err) {
    console.log('end');
});

Documentation for async.eachOfSeries can be found here.

You'll notice I didn't use a Promise here. This is largely because your asynchronousImageFunction is callback-based, as is the async library itself. My advice when dealing with async code is to try not flip back and forth between styles too often, or stuff gets hella confusing.

If you are able to make CommonJS modules work in your environment, there are promise-based variants of async. One of my favorites is here. With it, you can make the loop itself promise-based:

const pasync = require('pasync');

var images = {
  ABD: '1464685684713583388647.jpg',
  ABY: '1457524543088191607099.jpg',
  ADV: '1478877365443818880647.jpg',
  AFD: '1457527861824290195088.jpg',
}

function asynchronousImageFunction(key, image) {
    return new Promise((resolve) => {
        setTimeout(() => {
            console.log(key);
            console.log(image);
            resolve();
        }, Math.random() * 1000);
    });
}

pasync.eachOfSeries(
    images,
    (image, key) => asynchronousImageFunction(key, image)
)
    .then(() => {
        console.log('end');
    });

Comments

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.