Nahoot Nahoot - 7 months ago 31
Javascript Question

nodejs: How to wait for several asynchronous tasks to finish

I have a file where I'm writing things:

var stream = fs.createWriteStream("my_file.txt");
stream.once('open', function(fd) {
names.forEach(function(name){
doSomething(name);
});
stream.end();
});


This is working ok and I'm able to write to the file.
The problem is that the doSomething() function has some parts that are asynchronous. An example can be given with the dnsLookup function. Somewhere in my doSomething() I have:

dns.lookup(domain, (err, addresses, family) => {
if(err){
stream.write("Error:", err);
}else{
stream.write(addresses);
}
});


Now, my problem is, since the DNS check is asynchronous, the code keeps executing closing the stream. When the DNS response finally comes it cannot write to anywhere.
I already tried to use the async module but it didn't work. Probably I did something wrong.

Any idea?

Answer

Now that NodeJS is mostly up to speed with ES2015 features (and I notice you're using at least one arrow function), you can use the native promises in JavaScript (previously you could use a library):

var stream = fs.createWriteStream("my_file.txt");
stream.once('open', function(fd) {
  Promise.all(names.map(name => doSomething(name)))
  .then(() => {
    // success handling
    stream.end();
  })
  .catch(() => {
    // error handling
    stream.end();
  });
});

(The line Promise.all(names.map(name => doSomething(name))) can be simply Promise.all(names.map(doSomething)) if you know doSomething ignores extra arguments and only uses the first.)

Promise.all (spec | MDN) accepts an iterable and returns a promise that is settled when all of the promises in the iterable are settled (non-promise values are treated as resolved promises using the value as the resolution).

Where doSomething becomes:

function doSomething(name) {
  return new Promise((resolve, reject) => {
    dns.lookup(domain, (err, addresses, family) => {
      if(!err){                            // <== You meant `if (err)` here, right?
        stream.write("Error:", err);
        reject(/*...reason...*/);
      }else{
        stream.write(addresses);
        resolve(/*...possibly include addresses*/);
    });
  });
});

There are various libs that will "promise-ify" Node-style callbacks for you so using promises is less clunky than the mix above; in that case, you could use the promise from a promise-ified dns.lookup directly rather than creating your own extra one.

Comments