1

I have a modular JS app using external classes. It communicates with server side API and renders appropriate results. For learning purposes I want it to be pure vanilla JS without any frameworks and key libraries.

index.js

import Project from "./AppStructure/Models/Project.js";

let project;
let projectAPI = await fetch(projectUrl).then(res => res.json());

function onUserCreate() {
    project = new Project(projectAPI);
    project.loadObject3Ds();

    console.log(project);
    console.log(project.object3Ds);
    console.log(project.object3Ds[0]);

    project.object3Ds.forEach(function (object) {
        doSomething(object);
    });

function onUserUpdate() {
    requestAnimationFrame(onUserUpdate);
    renderSomething();
}

onUserCreate();
onUserUpdate();

Project.js

export default class Project {

    constructor(project) {
        this.object3DsIds = project.object3DsIds;
        this.object3Ds = new Array();
    }

    loadObject3Ds() {
        let object3Ds = new Array();

        this.object3DsIds.forEach(async function (object3DId) {
            let object3D = new Object3D();
            let objectUrl = 'https://localhost:44555/api/object3Ds/' + object3DId
            let object = await fetch(objectUrl).then(res => res.json());

            object3D.id = object.id;

            object3Ds.push(object3D)
        });

        this.object3Ds = object3Ds;
    }
}

The problem occurs when app is doing the forEach loop - simply nothing happens. It seems that it's initialised before object3Ds are pushed to project.object3Ds array.

When I console log project it seems to be complete but project.object3Ds array is a bit weird (preview is [], expanded is ok) and project.object3Ds[0] logs as undefined.

Why doesn't forEach loop wait for loadObject3Ds() method to be executed first? What am I doing wrong? Thank you for help.

BartoszPe
  • 41
  • 1
  • 4
  • 1
    forEach can't be async, you can use a for of loop. But the next issue is that the functions calling it aren't async either, it would all need to be async and every call awaited. – CherryDT Sep 30 '21 at 09:34
  • 1
    A forEach loop simply calls the function for each array element. If you want the results fast and in order, use Promise.all(). If you want the requests to run in sequence, use a for loop and await inside, that will run as expected. –  Sep 30 '21 at 09:37
  • 1
    By the way `new Array()` is not recommended, use `[]` instead – CherryDT Sep 30 '21 at 09:38
  • 1
    I updated the code to show what I mean: [index.js](https://gist.github.com/CherryDT/411aee3979a03aef750adc3c78fb6b20), [Project.js](https://gist.github.com/CherryDT/15652575d00d4ffc886ba3ef998e60ce) - as you can see, everything that does an asynchronous operation is now `async`, and when it's called it's `await`ed. "does something async" bubbles up in the structure of the code, because a caller of a function that "does something async" now also "does something async", etc. – CherryDT Sep 30 '21 at 09:38
  • Thank you @CherryDT for help. Is there a way to make these operations synchronous. All the following operations doesn't make any sense if the object3Ds are not loaded anyway. All I need is to make sure this fetch is completed before everything else will be proceeded. – BartoszPe Sep 30 '21 at 10:21
  • No, any operation that waits for something external to happen (a file read, a network request, an operation in another window or thread, etc.) is asynchronous because it would otherwise block the event loop because JavaScript is single-threaded. It would block the UI and any other code. That's why we have `async`/`await`, so we can write linear code even when it's asynchronous. – CherryDT Sep 30 '21 at 10:25
  • Thank you for help @ChrisG . Any chance for sharing the implementation for Promise.all() in this particular case? I'm trying to research it but it all gets much more complicted when using modular app construction and external classes. – BartoszPe Sep 30 '21 at 13:42
  • Thanks for reply @CherryDT. How come open source engines/libraries with loaders doesn't need async/await attribute for equivalents for onUserCreate() and onUserUpdate() ? Async operations are closing within external classes methods. Maybe i should not use Fetch API but something else instead? And can this question be reopened please? Conflict between async and forEach (which my question was reduced to and associated with) is only a small part of my problem (more a consequence). It's more about async in modular app and external classes. Thank you. – BartoszPe Sep 30 '21 at 13:52
  • `Promise.all`: You'd use `await Promise.all(this.object3DsIds.map(async object3DId => { /* ... */ }))` instead of `for (const object3Did of this.object3DIds) { /* ... */ }`. I'm not sure what exactly you are referring to with "open source engines/libraries with loaders" - can you show an example? – CherryDT Sep 30 '21 at 15:49
  • (Also, I don't think your question is eligible to being reopened right now. If the question is different from what you wrote originally, it would need to be edited to reflect that {right now it's "Why doesn't forEach loop wait for loadObject3Ds() method to be executed first? What am I doing wrong?" so the close as duplicate is correct}, but from what you said now - being a broader issue - it would also be either "missing focus" or "opinion-based", both also being close reasons. Maybe your question should be asked on a forum/discussion board instead of Stack Overflow?) – CherryDT Sep 30 '21 at 15:52
  • You need this: https://pastebin.com/JVGpb6GQ (note that your current code only copies the id and discards the rest, hence my `whatever` line). Next you need to use `await project.loadObject3Ds();` so the objects are all fully loaded before the function proceeds, and therefore you also need to make `onUserCreate` `async` –  Sep 30 '21 at 17:22

0 Answers0