0
\$\begingroup\$

Below I've written a simple version of what I'm attempting to accomplish. I'm new to asynchronous functions, so it's difficult to wrap my head around this. My goal is to wait for the loop to finish all the callbacks before console.log() runs, but I want all the callbacks inside the loop to be asynchronous, so they don't wait for calls to complete one after another, taking way too much time to complete. How would I write this, the loop to be synchronous (wait for all internal calls to complete) but the internal calls to be asynchronous?

Much appreciated!

var addressList = [];

for (var i = 0; i < members.length; i++) {
  service.member.info({
    name: members[i],
    company: "Cerberus",
    // Callback function
    function(body) {
      addressList.push(body.address);
    }
  });
}

console.log(addressList);
\$\endgroup\$
2
  • \$\begingroup\$ Synchronous waiting is bad in Node. The standard way to fix this would be to make the function containing your loop asynchronous. Is that a possibility? \$\endgroup\$
    – Ry-
    Commented Jul 2, 2016 at 19:29
  • \$\begingroup\$ I think so, if wrapping it in a function would help. As long as I can wait for all of the callbacks to finish before this function does the next callback. How would that look? \$\endgroup\$ Commented Jul 2, 2016 at 20:01

1 Answer 1

3
\$\begingroup\$

You can do this manually with a counter along these lines (kind of messy):

function getAddresses(members, callback) {
  var addressList = [];
  var count = 0;

  for (var i = 0; i < members.length; i++) {
    service.member.info({
      name: members[i],
      company: "Cerberus",
    }, function(body) {
      addressList[i] = body.address;
      count++;

      if (count === members.length) {
        callback(addressList);
      }
    });
  }
}

There are also libraries for this sort of thing, like async:

var async = require('async');

function getAddresses(members, callback) {
  async.map(members, function (member, callback) {
    service.member.info({
      name: member,
      company: "Cerberus",
    }, function (body) {
      callback(null, body.address);
    });
  }, callback);
}

But promises are really the best option if you’re able to use them consistently throughout your project, with a library like bluebird:

var bluebird = require('bluebird');

// This only works if the callback is really (err, body)
bluebird.promisifyAll(service.member);

function getAddresses(members) {
  return bluebird.map(members, function (member) {
    return service.member.infoAsync({
      name: member,
      company: "Cerberus",
    }).then(function (body) {
      return body.address;
    });
  })
}

They could take some learning, but they’re the future of asynchronicity in JavaScript or something, since right now you can do this (or you could, if service.member.info returned a promise) – note how close it is to your original loop:

var bluebird = require('bluebird');

var getAddresses = bluebird.coroutine(function* (members) {
  var addressList = [];

  for (var i = 0; i < members.length; i++) {
    var body = yield service.member.info({
      name: members[i],
      company: "Cerberus",
    });

    addressList.push(body.address);
  }

  return addressList;
});

// or, to run the loop in parallel:

var getAddresses = bluebird.coroutine(function* (members) {
  var operations = [];

  for (var i = 0; i < members.length; i++) {
    operations.push(
      service.member.info({
        name: members[i],
        company: "Cerberus",
      })
    );
  }

  return (yield operations).map(body => body.address);
});

and one day, that might be simplified to this, but not yet:

async function getAddresses(members) {
  var addressList = [];

  for (var i = 0; i < members.length; i++) {
    var body = await service.member.info({
      name: members[i],
      company: "Cerberus",
    });

    addressList.push(body.address);
  }

  return addressList;
}
\$\endgroup\$
2
  • \$\begingroup\$ Very extensive, thank you! I want to try each of these methods to fully grasp where callbacks are at right now. It feels like callbacks are something that I've put off for a bit too long, and they're much too important and powerful to ignore. I still feel like I'm missing a fundamental with how the callback is working in the first example. What would the function call look like? That may better help me understand. \$\endgroup\$ Commented Jul 2, 2016 at 23:42
  • \$\begingroup\$ @user3419168: getAddresses(members, function (addresses) { console.log(addresses); }) \$\endgroup\$
    – Ry-
    Commented Jul 3, 2016 at 4:56

Not the answer you're looking for? Browse other questions tagged or ask your own question.