Reputation: 709
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
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)
}
Promises and async stuff can get really confusing in js. But here are some rules of thumb:
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)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