2

Schrodinger's array: Is my array empty or full?

I am having a problem with accessing a particular array in my code. When I observe the array, it will observe correctly and will show the contents through the console. However, when I access the array, it will not let me as the array has no contents for me to access. What am I doing wrong?

Code below:

'use strict';

const DEBUG = true;

document.onreadystatechange = function () {
    if (document.readyState === 'complete') {
        if (DEBUG) {
            console.log('Info: Loading is complete.');
        }

        main();
    }
}

let main = function () {
    if (DEBUG) {
        console.log('Info: Running main thread.');
    }

    let cameras = [];

    let devices = navigator.mediaDevices.enumerateDevices().then(function (devices) {
        for (let i = 0; i < devices.length; i++) {
            let device = devices[i];

            if (device.kind === 'videoinput') {
                cameras.push(device);
            }
        }
    });

    console.log(cameras);
    console.log('cameras.length: ' + cameras.length);

    for (let i = 0; i < cameras.length; i++) {
        console.log('Loop' + i);
        console.log(cameras[i]);
    }

    console.log(cameras);
}

The output from the console:

app.js:8  Info: Loading is complete.
app.js:17 Info: Running main thread.
app.js:32 []
          0: MediaDeviceInfodeviceId: "881b49d744ed5bcf922d1cb834c33eaab43f31fcb955c2a4e6938a2a3fb3a46c"
            groupId: ""
            kind: "videoinput"
            label: "Logitech HD Webcam C615 (046d:082c)"
            __proto__: MediaDeviceInfodeviceId: (...)
          length: 1
          __proto__: Array(0)
app.js:33 cameras.length: 0
app.js:40 []
          0: MediaDeviceInfodeviceId: "881b49d744ed5bcf922d1cb834c33eaab43f31fcb955c2a4e6938a2a3fb3a46c"
            groupId: ""
            kind: "videoinput"
            label: "Logitech HD Webcam C615 (046d:082c)"
            __proto__: MediaDeviceInfodeviceId: (...)
          length: 1
          __proto__: Array(0)

The code is simple, it waits until the DOM is completely loaded and ready (No jQuery here). It then executes the function main(). This function creates the array cameras and then fetches all connected devices and pushes only the videoinput devices into the cameras array.

Now when I go to test the array to see if it contains anything I get two very different results.

  1. When I observe the array using console.log(cameras);, the console shows an array 1 device with its properties (Assuming 1 webcam is connected and working).

  2. When I access the array by any means (console.log(); or for () {} or cameras.forEach();), it will show nothing as if the array contained no contents. Testing the array length also shows 0.

Why is this? What am I doing wrong? Any help would be greatly appreciated.

NOTE: I am using Electron 1.7.10, Node 9.2.0 and NPM 5.6.0.

Jason
  • 21
  • 1
  • Due to `navigator.mediaDevices.enumerateDevices()` being asynchronous, things do not happen in the order you think they do. When you log `cameras` in `main()`, the array is still empty. `enumerateDevices()` finishes at some point in the future, long after you logged the array. Anything you want to happen to the full array needs to be moved inside the callback function. –  Jan 11 '18 at 15:08
  • 2
    Possible duplicate of [How do I return the response from an asynchronous call?](https://stackoverflow.com/questions/14220321/how-do-i-return-the-response-from-an-asynchronous-call) –  Jan 11 '18 at 15:09
  • That's what I thought, but why is that I am still able to access this array outside of the asynchronous function in the `main()` function by using `console.log(cameras);` and have it returns an array of contents yet when accessed like `console.log(cameras[i]);` it shows nothing?. – Jason Jan 11 '18 at 15:12
  • At the time the for loop runs, the array is empty => no "Loop" output. When you expand the `cameras` output in the console, Chrome shows the **current** contents, not the one at the time `.log()` was called. See here: https://stackoverflow.com/questions/24175017/google-chrome-console-log-inconsistency-with-objects-and-arrays –  Jan 11 '18 at 15:18
  • +1 for Schrodinger's array. I think because you've set the array to cameras is why when you do console.log(cameras); you get your awnser but anything else gives you empty arrays. – Scott Chambers Jan 11 '18 at 15:19
  • Now that makes sense, explaining the behaviour. Thank you! – Jason Jan 11 '18 at 15:19

3 Answers3

2

You make an asynchronous call. Meaning the array hasn't been filled on your console.log's

You probably want to use a callback:

let main = function () {
    if (DEBUG) {
        console.log('Info: Running main thread.');
    }

    let cameras = [];

    let devices = navigator.mediaDevices.enumerateDevices().then(function (devices) {
        for (let i = 0; i < devices.length; i++) {
            let device = devices[i];

            if (device.kind === 'videoinput') {
                cameras.push(device);
            }
        }
        callback();
    });

    function callback(){
        console.log(cameras);
        console.log('cameras.length: ' + cameras.length);

        for (let i = 0; i < cameras.length; i++) {
            console.log('Loop' + i);
            console.log(cameras[i]);
        }

        console.log(cameras);
    }
}
NullDev
  • 6,739
  • 4
  • 30
  • 54
  • 1
    Thanks for this, I should've mentioned I did try this before and it handled correctly due to the async nature of `navigator.mediaDevices.enumerateDevices()` but it didn't explain the unexpected behaviour. One of the comments to my initial question points out the underlying problem. But thank you once again for your effort! – Jason Jan 11 '18 at 15:25
0

console.log(cameras) display the array as it is when you expand it in the console not the way it was at the time it was logged, try to log it with console.log(JSON.stringify(cameras)); to see how it is at the time it is logged.

Gabriel Bleu
  • 9,703
  • 2
  • 30
  • 43
0

To answer my question for those curious, I can thank Chris G for pointing out why this behaviour was occurring instead of the expected behaviour.

At the time the for loop runs, the array is empty => no "Loop" output. When you expand the cameras output in the console, Chrome shows the current contents, not the one at the time .log() was called. See here: Google Chrome console.log() inconsistency with objects and arrays

Which makes sense as navigator.mediaDevices.enumerateDevices() being asynchronous in nature would of at the time of the for loop execution be still awaiting the resolution of its promise.

But why does the console.log(cameras); return a full array? Because of a bug that's still not resolved in Chrome.

See here: Google Chrome console.log() inconsistency with objects and arrays and https://bugs.webkit.org/show_bug.cgi?id=35801.

Jason
  • 21
  • 1