0

in an async function i, want to main function to return only in a specific point and not to return void at the end of the function

const fun = () => {

  const list = [];
  let streamFinished = 0;
  let streamCount = files.length;

  await fs.readdir(JSON_DIR, async(err, files) => {
    await files.forEach((filename) => {

      const readStream = fs.createReadStream(path.join("directory", filename));
      const parseStream = json.createParseStream();

      await parseStream.on('data', async(hostlist: Host[]) => {
        hostlist.forEach(async host => {
          list.push(host);
        });
      });

      parseStream.on('end', () => {
        streamFinished++;
        if (streamFinished === streamCount) {
          // End of all streams...
          return list; //this should be the only point when the function return something    
        }
      })

      readStream.pipe(parseStream);
    })
  });
};
Barmar
  • 741,623
  • 53
  • 500
  • 612
Mice Nic
  • 25
  • 4
  • This type of question is asked several dozen times a day on Stackoverflow [How to return values from async functions using async-await from function?](https://stackoverflow.com/questions/49938266/how-to-return-values-from-async-functions-using-async-await-from-function) – Andy Ray Jan 27 '23 at 00:46
  • You can't use `await` if the function isn't declared `async`. – Barmar Jan 27 '23 at 00:53
  • 2
    You cannot await something that does not return a Promise and both `fs.readdir` and `Array.prototype.forEach` don't return Promises. You need to wrap them in a Promise. In the case of `forEach` you can combine their results using `Promise.all` – slebetman Jan 27 '23 at 00:53
  • @Barmar It's not necessary for a function to be declared as async for it to be awaitable - it only needs to return a Promise. Functions declared as async always return a Promise but there are other ways to return a Promise. – slebetman Jan 27 '23 at 00:55
  • 2
    Please don't use a callback and promise at the same time. – code Jan 27 '23 at 00:55
  • @slebetman I meant you can't use `await` *inside* a function unless it's declared `async`. It needs to be `const fun = async () ..` – Barmar Jan 27 '23 at 00:56
  • i want the "list" to be the only return statement of the fun function. and by the way the function is built, it says that that a function must not return void. – Mice Nic Jan 27 '23 at 01:05
  • @AndyRay, i don't want the function to exit before the list is returned and return void, but in this way the function will return void. also im not sure. if the return statement is of the anonymous function and not the main function. but i want the return statement to be of the main function – Mice Nic Jan 27 '23 at 01:08
  • What is `json.createParseStream`? – Bergi Jan 27 '23 at 01:15
  • @Bergi, it was from big-json package.https://www.npmjs.com/package/big-json – Mice Nic Jan 27 '23 at 01:54

1 Answers1

1

There are lots of things wrong with this code. It's a big mix of event-driven streams, callback-driven functions and promises. The first order of business is to make the main business logic just be promise-driven (as you can see in the new parseData() function and switch all control flow outside of that to just use promises. Here's one way to do it:

const fsp = fs.promises;

function parseData(filename) {
    return new Promise((resolve, reject) => {
        const readStream = fs.createReadStream(path.join("directory", filename));
        const parseStream = json.createParseStream();
        const list = [];

        parseStream.on('data', (hostlist: Host[]) => {
            list.push(...hostlist);
        }).on('end', () => {
            resolve(list);
        }).on('error', reject);

        readStream.pipe(parseStream);
    });
}

const fun = async () => {

    const list = [];
    const files = await fsp.readdir(JSON_DIR);
    for (let filename of files) {
        const listData = await parseData(filename);
        list.push(...listData);
    }
    return list;
};

fun().then(result => {
    console.log(result);
}).catch(err => {
    console.log(err);
});

A few thoughts here:

  1. The easiest way to "wait" for a stream operation to complete is to encapsulate it in a promise which you can then use await or .then() with. That's the purpose of the parseData() function. In addition, this also enables error handling by hooking stream errors to the promise rejection.

  2. For processing a loop of asynchronous operations, you can either do them one at a time, using await on each asynchronous operation in the loop or you can run them in parallel by collecting an array of promises and using let data = await Promise.all(arrayOfPromises); on that array of promises.

  3. It is only useful to use await if the thing you're awaiting is a promise that is connected to your asynchronous operation. So, things like await parseStream.on('data', ...) and await files.forEach(...) are pointless because neither of those return promises. Do NOT just stick await in places unless you KNOW you are awaiting a promise.

  4. You will generally NOT want to use .forEach() with an asynchronous operation in the loop. Because .forEach() has no return value and no loop control, you can't really control much. Use a regular for loop instead which gives you full control. I consider .forEach() pretty much obsolete for asynchronous programming.

  5. Don't mix promises and callbacks and don't mix promises and streams. If you have those, then "promisify" the stream or callback so all your main logic/control flow can be promises. This will vastly simplify your code and make error handling possible/practical.

Bergi
  • 630,263
  • 148
  • 957
  • 1,375
jfriend00
  • 683,504
  • 96
  • 985
  • 979