Search code examples
javascriptnode.jspromiseasync-awaites6-promise

Images are not getting downloaded using promises


I need to download all images and generate word document with them. Using nodeJS and Meteor

WebApp.connectHandlers.use('/download', async function (req, res, next) {
  // ...

  const images = [];

  await lines.forEach(async (line, k) => {
    if (line.type && line.type === 'image') {
      images.push({
        id: line.id,
        file: line.id + '.jpg',
      });

      download_image(line.imageUrl, line.id + '.jpg');
    }
  });

  // ...

  // Then I use images[] to insert them into a Word document.
});

const download_image = (url, image_path) =>
  axios({
    url,
    responseType: 'stream',
  }).then(
    (response) =>
      new Promise((resolve, reject) => {
        response.data
          .pipe(fs.createWriteStream(image_path))
          .on('finish', () => resolve())
          .on('error', (e) => reject(e));
      })
  );

The problem is images are not getting downloaded before I insert the into a Word document.

How to stop/await before images are finished to be downloaded? I am not so good with promises. What is missing her?

Thanks!


Solution

  • its common mistake to use .forEach (or similar array methods) with async function inside it. The async function just mean that it returns promise and the await works in a same way like chaining together promises with then. Therefore this line wait lines.forEach(async (line, k) => { will just create and return bunch of promises, but it will not wait for all the promises inside to finish.

    WebApp.connectHandlers.use('/download', async function (req, res, next) {
      // ...
    
      const images = [];
      const promises = [];
      lines.forEach((line, k) => {
        if (line.type && line.type === 'image') {
          images.push({
            id: line.id,
            file: line.id + '.jpg',
          });
    
          promises.push(download_image(line.imageUrl, line.id + '.jpg'));
        }
      });
      // here you get array with all the images downloaded
      const downloadedImages = await Promise.all(promises);
      // this line will be executed after you download all images
    
      // ...
    });
    
    // This function would work same with or without the `async` keyword 
    // (because async function return promise - you are returning the promise. 
    // Async function allows to use await, but you are not using await in this function).
    // However it is good practice to have all functions that returns promise 
    // marked as `async` so you know that you receive promise from it.
    const download_image = async (url, image_path) =>
      // Dont forget to return your promise otherwise you cannot await it
      return axios({
        url,
        responseType: 'stream',
      }).then(
        (response) =>
          new Promise((resolve, reject) => {
            response.data
              .pipe(fs.createWriteStream(image_path))
              .on('finish', () => resolve())
              .on('error', (e) => reject(e));
          })
      );