Drublic
Drublic

Reputation: 709

Javascript ; loading data from url into object variable before running remaining code

I'm struggling to wrap my head around the javascript async/sync concepts.

I've subclassed an openlayers Layer class for some custom rendering in a map. After instantiating an object of the class, I want to load data (a png file from an url) and save the data into a class variable before I go on and do some custom webgl rendering based on the data. I've tried all sorts of stuff now with awaits and thens, but unable to get something to work. I was hoping someone could give me some pointers on how I should structure code to achieve this.

I'm able to do it when I load the data outside the class, but then only with a fixed url, and I need to be able to change the url on certain events.

Code for instantiating object and trying to run the function that sets the data in a class variable:

function update_map(time_change_event) {
    const new_url = 'www.example.com/' + time_change_event.detail.key
    const canvasLayer = new CanvasLayer({})
    canvasLayer.getData(new_url)
    map.addLayer(canvasLayer)
    map.removeLayer(*oldlayer*)
}

Code for custom class:

export class CanvasLayer extends Layer {

    data = null;

    getData(url){

        async data_promise = new Promise((resolve, reject) => {
            const image = new Image();
            image.onload = () => {
                const canvas = document.createElement('canvas');
                canvas.width = image.width;
                canvas.height = image.height;
                const context = canvas.getContext('2d');
                context.drawImage(image, 0, 0);
                const data = context.getImageData(0, 0, width, height);
                resolve(data);
            
            };
            image.onerror = () => {
                reject(new Error('failed to load'));
            };

            image.src = url;
        })
    
        this.data = await data_promise;

    }

    render(frameState, target){
        // This is called when layer is added to map
        // Doing stuff with this.data does not work because it is null
        console.log(this.data) returns null
    }
}

Upvotes: 0

Views: 67

Answers (1)

Ikehunter5
Ikehunter5

Reputation: 81

To expound on what @David was saying, there's a few things at play here with promises and async functions. Here's one option using just plain promises:

export class CanvasLayer extends Layer {
  data = new Promise()

  getData(url) {
    this.data = new Promise((resolve, reject) => {
      const image = new Image()
      image.onload = () => {
        const canvas = document.createElement('canvas')
        canvas.width = image.width
        canvas.height = image.height
        const context = canvas.getContext('2d')
        context.drawImage(image, 0, 0)
        const data = context.getImageData(0, 0, width, height)
        resolve(data)
      }
      image.onerror = () => {
        reject(new Error('failed to load'))
      }

      image.src = url
    })
  }

  render(frameState, target) {
    this.data.then((imageData) => {
      console.log('Image data:', imageData)
    })
  }
}

What this does is creates a new Promise and stores it in the class for later use. When you call render, you essentially just hook into that promise and tell it to run a function when the promise resolves.

You can also make the function async and have something like this:

async render(frameState, target) {
  let imageData = await this.data
  console.log('Image data:', imageData)
}

Explanation

Promises and async stuff can get really confusing in js. But here are some rules of thumb:

  1. await can only be used in an async function (as of right now in 2024, though this might change with some new js features like top level awaits)
  2. Promises are an object like anything else, its essentially your way of saying in the code "this won't have a value immediately, but I promise it will eventually arrive".
  3. You can either get a value from a promise by awaiting it, or by chaining a then to it; but either way you need to tell the code to "just wait until this value comes, you don't need to do anything else until then."

Here's some more info on async functions: async function - developer.mozilla.org

And some info on promises: Promise - developer.mozilla.org

Upvotes: 1

Related Questions