'How to pass an additional argument to the callback function in the Three.js loader.parse method

Parse method of Loader object in three.js allow you to set a callback function that is called when the parsing process is complete. This callback is called passing it a unique argument that is the object parsed.

My problem is that i need an other argument to be passed to the callback. This because i use parse method in a loop and i want create many callback eachone with a specific value of a variable.

If i set this value in the loop but outside the callback when the callback is executed unavoidably and obviously the value is always the last one setted in the loop.

This is the code:

for(var foldcont_index in foldcont) {

    var foldit= foldcont[foldcont_index];

    if(foldit.isDirectory()) { loadBFiles(fold+'/'+foldit.name); }

    if(foldit.isFile()) {

      var buigltf= fs.readFileSync(fold+'/'+foldit.name, 'utf8');
      loader.parse(
          buigltf,
          undefined,
          function(o) {
             var oname= // !!! before issue with foldit.name
             objectstank['xxx_'+oname]= o;
             loadpoint= loadpoint+loadpercentage;
             loadbar.set(loadpoint);
             if(loadpoint>= 100) { document.getElementById("load-bar").style.display= 'none'; },
          undefined
          }
        );

    }

}

Can somebody help me to find a solution?



Solution 1:[1]

Read up on closures in Javascript

Unlike languages like C/C++ it's trivial to "close over variables" in JavaScript so you never need extra parameters for a callback in JavaScript because you can always "close over" whatever variables you need access to in the callback using a closure

In your case

for(var foldcont_index in foldcont) {

    var foldit= foldcont[foldcont_index];

    if(foldit.isDirectory()) { loadBFiles(fold+'/'+foldit.name); }

    if(foldit.isFile()) {

      var buigltf= fs.readFileSync(fold+'/'+foldit.name, 'utf8');
      loader.parse(
          buigltf,
          undefined,
          function(oname) {
              return function(o) {
                  var oname= // !!! before issue with foldit.name
                  objectstank['xxx_'+oname]= o;
                  loadpoint= loadpoint+loadpercentage;
                  loadbar.set(loadpoint);
                  if(loadpoint>= 100) { document.getElementById("load- bar").style.display= 'none'; }
              };
          }(foldit.name),
          undefined
      );

    }
}

might work. (I can read your code). This is no Loader object I know of in Three.js. There are lots of XXXLoader objects. No clue which one you're using.

This pattern

function(var1, var2, var3) {
   return function() {
      // do something with var1, var2, var2
   };
}(value1, value2, value2);

Is a common pattern for closing over values. The code is a function that returns a function that has "closed" over var1, var2, and var3.

So you can pass the returned function to a callback. Long hand example

function makeCallback(var1, var2, var3) {
   return function() {
     console.log(var1, var2, var3);
   };
}

const fn = makeCallback('Hello', 'World', '!');
setTimeout(fn, 1000);

The inline version

for (let i = 1; i <= 4; ++i) {
  setTimeout(function(var1) {
     return function() {
        console.log(var1);
     };
  }(i), i * 500);
}

Solution 2:[2]

The following code might serve as a helpful example as it is designed to be as self-explanatory as I could make it. The callback function adds "initially invisible" models to the scene and also adds them to a list. Other methods in the class (not shown) take care of deciding dynamically when individual models should be made visible and where they should be placed in the scene.

import { GLTFLoader } from 'https://cdn.skypack.dev/[email protected]/examples/jsm/loaders/GLTFLoader.js'

export class transitVehicleSystem {

  constructor(scene, dParamWithUnits, numWedges) {

    this.scene = scene
    this.unallocatedTransitVehicleModels = []

    function prepareACallbackFunctionForLoader(myScene, myList) {
      return function( {scene} ) {
        const object = scene.children[0]
        object.visible = false
        for (let i=0; i<dParamWithUnits['numTransitVehicleModels'].value; i++) {
          const tempModel = object.clone()
          myScene.add(tempModel)
          myList.push(tempModel)
        }
      } 
    }
    const addTransitVehicles = prepareACallbackFunctionForLoader(this.scene, this.unallocatedTransitVehicleModels)

    const loader = new GLTFLoader()
    loader.load('models/TransitCar.glb',
      // pass in the callback function that was created within a closure
      addTransitVehicles,
      // called when loading is in progresses
      function ( xhr ) {
        console.log( ( xhr.loaded / xhr.total * 100 ) + '% transit car loaded' );
      },
      // called when loading has errors
      function ( error ) {
        console.log( 'An error happened', error );
      }
    )
  
    console.log(this.unallocatedTransitVehicleModels)
  
  }
*
*

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 gman
Solution 2 phil1008