Home > Net >  Why am I getting 'undefined' and empty array[] from a Promise.all call involving JavaScrip
Why am I getting 'undefined' and empty array[] from a Promise.all call involving JavaScrip

Time:03-19

As part of my personal project that makes use of Azure AI APIs and Node.js/Express, I'm trying to respond to a get request to a /viewText route with text and key phrases extracted from an uploaded image/document.

Here's the code that should log that data to the console:

app.get(`/viewText`, async (req, res) => {
  const answerReadResult = await readOperation(`${__dirname}\\uploads\\answer`);
  const markReadResult = await readOperation(`${__dirname}\\uploads\\mark`);
  
  Promise.all([
    readOperation(`${__dirname}\\uploads\\answer`),
    keyPhraseExtractor(answerReadResult),
    readOperation(`${__dirname}\\uploads\\mark`),
    keyPhraseExtractor(markReadResult),
  ]).then((data) => {
    console.log("data from Promise.all: ", data);
  });
});

I expected to receive two strings from the readOperation function and an array of strings from the keyPhraseExtractor, but all I get is: data from Promise.all: [ undefined, [], undefined, [] ]

Here's the code for the readOperation function(which takes an image and returns the identifiable text as an array of strings)

const readOperation = async (path) => {
  fs.readdir(path, (err, files) => {
    if (err) console.log(err);

    files.forEach(async (file) => {
      try {
        const results = await getTextFromImage(
          `${__dirname}\\uploads\\answer\\${file}`
        );
        console.log("data from readOperation: ", results.join(" "));
        return results;
      } catch (err) {
        console.error(err);
      }
    });
  });
};

and the log is: data from readOperation: you must be the change you want to see in the world !

N.B: this logged data is what I'm trying to return from this function (without the join("")) but I'm getting undefined as is apparent from the Promise.all

The keyPhraseExtractor function (shown below) expects an array of strings and should return the key phrases in those strings (also as an array of strings)

const keyPhraseExtractor = async (dataFromReadOperation) => {
  let results = dataFromReadOperation;
  try {
    const data = await keyPhraseExtraction(textAnalyticsClient, results);
    console.log("data inside keyPhraseExtractor: ", data);
    return data;
  } catch (err) {
    console.error(err);
  }
};

The log is data inside keyPhraseExtractor: []

CodePudding user response:

readOperation is wrong. Within the async function you're kicking off readDir and passing it a callback, but nothing in the async function waits for the callback value (so readDir is likely not done by the time it returns), and there's no return in readOperation, so it absolutely returns undefined.

Yes, you have return results, but that's within the forEach async arrow function. There's nothing that would pass that out to the enclosing promise that readOperation returns.

// This is not async, because it's returning a new Promise anyway.
const readOperation = /* NOT ASYNC */ (path) => {
  return new Promise((resolve, reject) => {
    fs.readdir(path, (err, files) => {
      if (err) {
        console.log(err);
        reject(err);       // Outer. Don't forget to stop here.
      }

      // Resolve the outer promise with an inner promise
      // from Promise.all, so getTextFromImage can run in
      // parallel. Also use `map` instead of `forEach` so the
      // return value is an array of Promises (due to the async).
      resolve(Promise.all(files.map(async (file) => {
        try {
          const results = await getTextFromImage(
            `${__dirname}\\uploads\\answer\\${file}`
          );
          console.log("data from readOperation: ", results.join(" "));
          return results.join(" "); // each file's return should
                                    // be a string, right?
        } catch (err) {
          console.error(err);  // Inner.
          throw err;           // Re-throw so it rejects the promise.
        }
      })));
    });
  });
};

That said, I'd suggest using the fs Promises API so you're not mixing err-first callback APIs and async functions.

// This is async, because you can await the Promises inside.
const readOperation = async (path) => {
  let files;
  try {
    files = await fsPromises.readdir(path);
  } catch (err) {
    console.log(err);  // Outer.
    throw err;
  }
  // Still use Promise.all here, so the mapping can happen in parallel.
  // You could await this, but async functions that return Promises will
  // unwrap the results anyway: it only affects where rejections come from.
  return Promise.all(files.map(async (file) => {
    try {
      const results = await getTextFromImage(
        `${__dirname}\\uploads\\answer\\${file}`
      );
      console.log("data from readOperation: ", results.join(" "));
      return results.join(" ");
    } catch (err) {
      console.error(err);  // Inner.
      throw err;           // Re-throw so it rejects the promise.
    }
  });
};
  • Related