1

I'm trying to push an instance of a class to an array within a forEach loop, but nothing gets pushed to the array for some reason.

The loop is inside a class method, and right up until the console.log, everything looks fine (The Device code is tested and working , the Device.build() method populates some member variables inside the Device)

class DeviceManager {
constructor() {
    this.deviceList = [];
}

async buildDevices() {
    const deviceNames = await this.getDeviceNames();

    deviceNames.forEach(async name => {
      const device = new Device(name);
      await device.build(); 
      console.log(device); // This outputs the device as expected!
      this.deviceList.push(device); // However, the device doesn't end up in this array?
    });
  }
...
...
}

I create an instance of DeviceManager, then call await DeviceManager.buildDevices().

After this, I expect deviceManager.deviceList to be full of the devices, however it's empty, all I get back is []

What's going on here? Does anyone have an explanation?

Jason
  • 412
  • 1
  • 6
  • 13
  • What happens if you add `console.log(this.deviceList)` after `this.deviceList.push(device);` ? Do you see the list grow in the console as expected? – Dacre Denny Jun 25 '19 at 02:49
  • 1
    I believe the next can help you: https://stackoverflow.com/questions/37576685/using-async-await-with-a-foreach-loop. There is a suggestion to use `for ... of` instead of the `forEach()`. – Shidersz Jun 25 '19 at 02:50
  • deviceNames is just an array of strings, ie `['deviceA', 'deviceB']`. Adding a `console.log(this.deviceList)` after pushing the device to the list gives me the expected result in the console: I can see the deviceList growing as devices are added. – Jason Jun 25 '19 at 02:52
  • I don't think this is a duplicate. Using a for...of has the same effect. You will see the deviceList filled at the end of the buildDevices method, but outside of that method you will still see an empty array – Mark Jun 25 '19 at 03:18

1 Answers1

0

Because you are calling an 'forEach' as async, you may be testing the devices list too early. Perhaps try await the forEach loop aswell, to ensyre the buildDevices function is only resolved after the forEach loop is finished.

i.e

class DeviceManager {
  constructor() {
      this.deviceList = [];
  }

  async buildDevices() {
      const deviceNames = await this.getDeviceNames();

      // Convert to 'for' loop and await for async calls
      for (let index = 0; index < deviceNames.length; index++) {
        let name = deviceNames[index];
        const device = new Device(name);
        await device.build(); 
        console.log(device); // This outputs the device as expected!
        this.deviceList.push(device); // However, the device doesn't end up in this array?
      }
    }
}
twomarktwo
  • 332
  • 1
  • 11
  • thanks for the suggestion... unfortunately no luck, I still get the same result even with `await`ing the loop. – Jason Jun 25 '19 at 02:49
  • I apologize, forEach is not awaitable see this article https://codeburst.io/javascript-async-await-with-foreach-b6ba62bbf404. I have updated the suggestion to convert the forEach to a conventional 'for' loop. and await internally for the `device.build();` in each loop – twomarktwo Jun 25 '19 at 03:02
  • After the for loop is complete you will see the filled deviceList array inside the buildDevices method, but the caller of buildDevices will see an empty array immediately after calling buildDevices – Mark Jun 25 '19 at 03:19
  • You can do this: wrap the body of the buildDevices method in a Promise and have the it resolve like this: return new Promise(async (resolve, reject) => { ... resolve(this.devicesList); then the caller can do this: const d = new DeviceManager(); d.buildDevices().then((deviceList) => { console.log(deviceList) console.log(d.deviceList) }); Not great solution though, kind of defeats the purpose of encapsulating data in the class – Mark Jun 25 '19 at 03:31