Chris Gabler
Chris Gabler

Reputation: 179

How to await for a promise with an inner promise to be resolved?

I have an express app and want to return response only after the file is downloaded and decompressed. My code looks like this:

function downloadObject() {
  return getObject(...).then((archive) => {
    console.log("Downloaded.");
    fs.writeFileSync(...);
    return decompress(..., function(err) {
      if (err) {
        console.log("Decompression error.");
      } else {
        console.log("Decompressed");
      }
    });
  })
}

app.post("/download", async function (req, res) {
  await downloadObject();
  res.send('ready');
});

It does wait until the file is downloaded, however, it returns response before the file is decompressed. How can I make it wait for the inner promise to be resolved too?

Upvotes: 0

Views: 131

Answers (2)

boehm_s
boehm_s

Reputation: 5534

You are actually returning the result of the decompress function, but it's an async function, so the return value doesn't really matter and is probably undefined.

What matters is the callback that you pass in, whose signature probably looks like the following :

decompress(..., function(err, result) {
   // Do what you want with the error and the result
})

To use promise instead of callback to achieve your asynchronous task, you can wrap your function in a promise like the following, and since your inside the then of another Promise, it will chain.

function downloadObject() {
  return getObject(...).then((archive) => {
    console.log("Downloaded.");
    fs.writeFileSync(...);
    return new Promise((resolve, reject) => 
      decompress(..., function(err, result) {
        if (err) {
          reject(err);
        } else {
          resolve(result);
        }
      })
    );
  })
}

That's not all, it works, but we can do better. writeFileSync will block your IO in Node and we don't want that. Instead, it is preferable to use writeFile and everything would be cleaner using Promise chaining :

function downloadObject() {
  return getObject(...)
    .then((archive) => new Promise((resolve, reject) => {
       fs.writeFile("<filename>", archive?.data, '<file-encoding>', err => {
         if (err) {
           reject(err);
         } else {
           resolve(archive);
         }
       })
    }))
    .then((archive) => new Promise((resolve, reject) => {
      decompress(archive, function(err, result) {
        if (err) {
          reject(err);
        } else {
          resolve(result);
        }
      })
    }));
}

Of course, it's pseudo-code, I don't know what is in your archive object, nor do I know the type signature of your decompress function, but the point is to show how to wrap callback-style function in Promises, chain them and also handle errors properly with the reject function provided by the Promises.

Upvotes: 2

Botje
Botje

Reputation: 30807

Without knowing too much about decompress, I did notice it takes a callback. That means it is itself an asynchronous function and immediately returns.

Use util.promisify to turn it into a regular promise-producing function:

async function downloadObject() {
  const archive = await getObject(...);
  console.log("Downloaded.");
  fs.writeFileSync(...); // Or `util.promisify(fs.writeFile)`
  try {
    await util.promisify(decompress)(...);
    console.log("Decompressed");
  } catch (err) {
    console.log("Decompression error.");
  }
}

This function implements a common pattern that was mentioned in a now-deleted answer:

util.promisify = function (f) {
    return function(...args) {
       return new Promise((resolve, reject) => {
          f(...args, (err, result) => { err ? reject(err) : resolve(result); });
       })
    }
}

Upvotes: 1

Related Questions