简体   繁体   中英

Using for..in loop with Promises and asynchronous functions

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?

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

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

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM