Collect values into an array in an async function

Hi all!

I have this code - a function that should return all values from files in a FileSystem directory:

getAllItems: async (dir) => {

    let uri = val = null;
    let collect = [];
    await FileSystem.readDirectoryAsync(dir)
    .then (itms => {
        itms.map(async k => {
          uri = `${dir}/${k}/`;
          await FileSystem.readAsStringAsync(uri)
          .then ( 
            val => {
              collect.push(val)
              console.log('getAllItems',`key: ${k}, val: ${val}, collectlen ${collect.length}`)}
                    /*  console.log result of iteration:
                          getAllItems key: skipIntro, val: false, collectlen 1
                          getAllItems key: fontSize, val: 16, collectlen 2
                          getAllItems key: theme, val: default, collectlen 3
                    */
          );
        });
    })
    .catch(err => {console.log('getAllItems.',err)});
    
    console.log('getAllItems return', `collect: ${collect}`)  // result: getAllItems return collect  (collect variable is empty)
    return collect;
    
}

In the map cycle, as you can see, everything works correctly.

Obviously, and I understand that return fires before collect gets all the values into an array.
But all attempts to place return elsewhere in the code and other methods have not yet given the desired result.
Unfortunately, I did not find any suitable tips for my case on the Internet either.

Maybe someone can help here?

Thanks in advance…

Hi @latglas

This is actually a question about async/await/Promises in JavaScript and not really anything to do with Expo.

I struggle a bit with that stuff too, but I think I was able to make sense of what you’re trying to do and simplify the code, but I can’t promise it’s 100% correct :sweat_smile: (no pun intended).

As far as I understand it you are trying to read the contents of all of the files in a directory and return those as an array?

Thoughts:

  • My first thought is that maybe using some sort of KV store (like AsyncStorage) would be a better way of doing this.
  • You’re calling itms.map(...) but not doing anything with the results. That’s not how .map() is intended to be used. If you’re using .map(), you should probably avoid side effects in the function called by .map() and you should use the results.
  • As you point out, you’re trying to push stuff onto a collection that is going to disappear before the code has finished pushing values onto it.

To make this easier to experiment with I used nodejs instead of Expo. I created a directory containing three files, like this:

mkdir /tmp/blah
cd /tmp/blah
echo 1 >a
echo 2 >b
echo 3 >c

I then created a file with the following contents and saved it as /tmp/getItems.js:

const fs = require("fs/promises");

const getAllItems = async (dir) => {
  const items = await fs.readdir(dir);
  console.log("filenames", items);
  // items.map(...) will return an array of promises, each of which resolves to a string.
  // Promise.all() converts that into a promise that resolves to an array of strings.
  const contents = Promise.all(
    items.map(async (item) => await fs.readFile(`${dir}/${item}`, "utf8"))
  );
  return contents;
};

const doStuffWithItemContents = async () => {
  console.log(await getAllItems("/tmp/blah"));
};

doStuffWithItemContents();

If I run it using node /tmp/getItems.js, it outputs the following:

filenames [ 'a', 'b', 'c' ]
[ '1\n', '2\n', '3\n' ]
1 Like

Thank you, @wodin.

Above in my post, I simply shortened the code. Actually the result of the .map will be used.

About the rest. I want to make a component that would be useful for other Expo users as well. Therefore, I would like to make it as simple and fast as possible and would not want the user to be forced to upload other libraries to use the component.

But, in any case, after your answer, I had an idea how to implement what I had in mind. If it works, I’ll post it here later.

Thank you again! :slight_smile:

I mean you should do something like:

// storing the result of the .map() call
const upperCaseItems = itms.map((item) => item.toUpperCase());

or whatever. Not something like:

let upperCaseItems = [];
// Ignoring the result of the .map() call and just using it for side effects
// If you want to do something like this, rather use a loop
itms.map((item) => {
  upperCaseItems.push(item.toUpperCase); // <-- side effect
});

If you save this in a file and run it you will see that the result of the second .map() call is [ undefined, undefined, undefined ].

const itms = ["a", "b", "c"];

const upperCaseItems = itms.map((item) => item.toUpperCase(item));
console.log("upperCaseItems", upperCaseItems);

let upperCaseItems2 = []
const result = itms.map((item) => {
  upperCaseItems2.push(item.toUpperCase());
});
console.log("result", result);

output:

upperCaseItems [ 'A', 'B', 'C' ]
result [ undefined, undefined, undefined ]
1 Like