Ali Ahmadi
Ali Ahmadi

Reputation: 731

Unexpected result when using For Loop instead of forEach

I am trying to iterate over the selected files in input, i got a weird bug that happens when i switch the below statement

Object.keys(event.target.files).forEach(key => {
   const currentFile = event.target.files[key]
}

to

for (let key in event.target.files) {
   const currentFile = event.target.files[key]
}  

I don't understand whats the difference between these two statements, the first one works but the second one doesn't (results in weird bug where an object inside the loop becomes null.

Here is the full code :

onChange= (event) => {
        const selectedFilesObject = event.target.files
        let selectedFilesCount = Object.keys(selectedFilesObject).length

        if (selectedFilesCount > 0) {
            this.setState({ loading: true })
            this.props.onLockdownChange(true)

            let readFilesResult = {}

            for (let key in selectedFilesObject) {
                const currentFile = selectedFilesObject[key]

                readFileAsByteArray(currentFile)
                    .then(response => {
                        readFilesResult[key] = {
                            bytes: response,
                            name: currentFile.name,
                            type: currentFile.type,
                        }
                    })
                    .catch(() => {
                        readFilesResult[key] = null                        
                    })
                    .finally(() => {
                        selectedFilesCount = selectedFilesCount - 1
                        if (selectedFilesCount === 0) {
                            this.onReadingFilesFinished(readFilesResult)
                        }
                    })
            }
        }
    }  


export const readFileAsByteArray = (file) => {
    return new Promise((resolve, reject) => {
        var reader = new FileReader();
        var fileByteArray = [];
        reader.readAsArrayBuffer(file);
        reader.onloadend = (evt) => {
            if (evt.target.readyState == FileReader.DONE) {
                var arrayBuffer = evt.target.result,
                    array = new Uint8Array(arrayBuffer);
                for (var i = 0; i < array.length; i++) {
                    fileByteArray.push(array[i]);
                }

                resolve(fileByteArray)
            }
        }

        reader.onerror = error => {
            reject()
        };
    })
}  

I just need to understand why using for loop causes readFilesResult to have a null length! but using object.keys().forEach doesn't!

Upvotes: 0

Views: 181

Answers (2)

Pirhan
Pirhan

Reputation: 186

The difference between these two loops comes down to whether an object actually has a property, or inherited a property.

For example, if you have an object that inherited a property; Object.keys will not return it, but a for in loop will. Which is why you need to call hasOwnProperty method for each key you have in a for in loop.

In a more detailed way; for in loop is not bothered by the prototype chain.

// lets create a class called "Example".

class Example{
   constructor(a,b) {
      this.a = a;
      this.b = b;
   }
}

let instance = new Example(1,2);
for(let key in instance) {
    console.log(key); // will only print "a" and "b". Which is to be expected.
}

// Let's add a member to all of the objects that are instantiated from the class Example using Example's prototype.

Example.prototype.newMember = "I exist";

// Let's run the for in loop again

for(let key in instance) {
    console.log(key); // Prints "a", "b", and "newMember". The instances that are coming from "Example" doesn't have this... What happened there?
}

Turns out when you run a for-in loop, the loop doesn't recognize the prototype properties, and runs on all of them. Which is why I suggested running hasOwnProperty check on your example. hasOwnProperty checks to see if the member actually has a property, or is it a prototype property.

Does that make any sense?

Upvotes: 2

Bergi
Bergi

Reputation: 664385

You're enumerating a FileList here, which as a DOM collection has item and length properties. You should use neither Object.keys nor a for … in loop here, rather a for (let i=0; i<files.length; i++) iteration like on any array-like object. See also Why is using for…in on arrays such a bad idea?.

Or just convert it into an array right away:

onChange = (event) => {
    const selectedFiles = Array.from(event.target.files)
//                        ^^^^^^^^^^
    if (selectedFiles.length > 0) {
        this.setState({ loading: true })
        this.props.onLockdownChange(true)

        Promise.all(selectedFiles.map(currentFile =>
            readFileAsByteArray(currentFile).then(response => ({
                bytes: response,
                name: currentFile.name,
                type: currentFile.type,
            }), err => null)
        )).then(readFilesResult => {
            this.onReadingFilesFinished(readFilesResult)
        })
    }
}

Upvotes: 3

Related Questions