Im stuck in trying to get imageData of base64 image. I have 2 functions, and the problem is that second function starts before first ends.
I have 2 functions
frame.restoreImageDataForLayers();
frame.changeResultLayer();
first - load image on a page and then change variable 'layers.data' to imageData of that loaded image.
second - work with imageData of image.
restoreImageDataForLayers() {
this.layers.forEach((item) => {
const layer = item;
const canvas = document.createElement('canvas');
const context = canvas.getContext('2d');
const image = new Image();
image.onload = function drawImg() {
context.drawImage(image, 0, 0);
layer.data = context.getImageData(0, 0, layer.canvasSize, layer.canvasSize).data;
console.log('FIRST', layers.data');
};
image.src = layer.data;
});
}
changeResultLayer() {
console.log('SECOND', this.layers[0].data);
}
And then i have
second console log before
first console log
and my another code that must work with imageData - crash.
How to fix that?
You will want to convert restoreImageDataForLayers into a function that returns a promise, whose promise only resolves when the operations in the function complete.
restoreImageDataForLayers() {
const layerPromises = this.layers.map(item => {
return new Promise((resolve, reject) => {
// All your canvas-image code here...
image.onload = function(){
// Your onload code here
// Then resolve the promise for this item
resolve()
}
})
})
return Promise.all(layerPromises)
}
To do that, you need to wrap your image loading procedure in a promise. Use array.map() to create an array of these promises, one for each item. Then use Promise.all() to create that one promise that resolves only when all of the promises in the array resolves.
To call this function, you simply do this:
frame.restoreImageDataForLayers().then(() => {
frame.changeResultLayer();
});
Or if the caller is in an async function, do this:
async thatWrapperFunction(){
await frame.restoreImageDataForLayers();
frame.changeResultLayer();
}
Related
The following function takes and image from an url, loads it, and returns its width and height:
function getImageData (url) {
const img = new Image()
img.addEventListener('load', function () {
return { width: this.naturalWidth, height: this.naturalHeight }
})
img.src = url
}
The problem is, if I do something like this:
ready () {
console.log(getImageData(this.url))
}
I get undefined because the function runs but the imaged hasn't loaded yet.
How to use await/async to return the value only when the photo has loaded and the width and height is already available?
How to use async/await to turn this callback function into a promise?
You don't. As usual, you use the new Promise constructor. There's no syntactic sugar for that.
function loadImage(url) {
return new Promise((resolve, reject) => {
const img = new Image();
img.addEventListener('load', () => resolve(img));
img.addEventListener('error', reject); // don't forget this one
img.src = url;
});
}
How to use await/async to log the value only when the photo has loaded and the width and height is already available?
You can do
async function getImageData(url) {
const img = await loadImage(url);
return { width: img.naturalWidth, height: img.naturalHeight };
}
async function ready() {
console.log(await getImageData(this.url))
}
This library works pretty well - it allows connection to the child process or simply returns the result asynchronously if desired: https://github.com/expo/spawn-async
This question already has answers here:
How to turn this callback into a promise using async/await?
(2 answers)
Closed 3 years ago.
I have written a promise based loading image function.
function loadImage() {
const img = new Image();
return new Promise((resolve, reject) => {
img.addEventListener("load", () => {
resolve();
});
img.addEventListener("error", () => {
reject();
});
img.src = 'assets/myImg.jpg';
});
}
loadImage.then(
() => console.log('image loaded'),
() => console.error('image did not load')
);
I would like to convert it using the async/await but I am actually struggling to do it. Any idea how it can achieved ?
You can await your loadImage function, but the new Image() object doesn't use promises, it uses events which you've wrapped with a Promise.
Inside of an async function, you can await your loadImage function, like so:
async function loadImage() {
const img = new Image();
return new Promise((resolve, reject) => {
img.addEventListener("load", () => {
resolve();
});
img.addEventListener("error", () => {
reject();
});
img.src = 'assets/myImg.jpg';
});
}
async function doSomething() {
try {
await loadImage();
console.log("image loaded");
} catch(e) {
console.error("image did not load");
}
}
With a couple changes you can make it work.
The async await side of things would generally replace the ".then()" chaining.
First of all, keep the loadImage() function as is, and define an additional "async" function to run your async await code in. I've called this "main()". You can then create a variable, for example "loadedImage", and then await the promise function. This will pause execution of the next step (being the console log in this case) until the promise has resolved/rejected and the image is loaded.
async function main() {
let loadedImage = await loadImage();
console.log(loadedImage);
}
main()
Additionally, if you wanted to actually return a value into the loadedImage variable (as right now it will be undefined), inside your promise "resolve()" you can place a value. Eg:
resolve('the image has loaded')
I'am trying to learn how to make SuperMario in JavaScript from here
Can someone explain flow of the below function LoadImage ?
function loadImage(url) {
return new Promise(resolve => {
const image = new Image();
image.addEventListener('load', () => {
resolve(image);
});
image.src = url;
});
}
const canvas = document.getElementById('screen');
const context = canvas.getContext('2d');
context.fillRect(0,0,50,50);
loadImage('/img/tiles.png')
.then(image=>{
context.drawImage(image,0,0); // the function LoadImage returns a Promise with image object(which is a constant)
// as parameter and if the promise is fulfilled then the image is drawn.
/
});
EDIT
I do understand how to use => operator.
Which is used to make length of functions smaller.
image.addEventListener('load', () => {
resolve(image);
the above line means that the promise is fulfilled when image is loaded.
So does this mean that the following line is executed and then the event listener is waiting for the image to be downloaded in client browser ?
image.scr = url;
The flow of the below function is a little fuzzy to me
function loadImage(url) {
return new Promise(resolve => {
const image = new Image();
image.addEventListener('load', () => {
resolve(image);
});
image.src = url;
});
EDIT 2:
Okay, this was a stupid post. And yup as the IMAGE from url is loaded in the image object then Event listener fires up the resolve().
The code you are showing introduces an asynchronous primitive, Promise, which can be passed around and used to access a resource that hasn't been populated yet.
In this case, you want an Image that is fully loaded and has image data that you can use. However, you can't access the image data until a network request finishes that would fetch the image data.
For example, this won't work:
const img = new Image();
img.src = "example.com/house.jpg";
ctx.drawImage(img, 0, 0); // img isn't done loading yet
Instead, we have to wait until the loading is done. There are a lot of ways to do that but the most common conventions are to use, callbacks, Promises, or async/await.
The method that you have shown combines callbacks and Promises (out of necessity).
Let's break down the code:
/**
* Load an image from a given URL
* #param {String} url The URL of the image resource
* #returns {Promise<Image>} The loaded image
*/
function loadImage(url) {
/*
* We are going to return a Promise which, when we .then
* will give us an Image that should be fully loaded
*/
return new Promise(resolve => {
/*
* Create the image that we are going to use to
* to hold the resource
*/
const image = new Image();
/*
* The Image API deals in even listeners and callbacks
* we attach a listener for the "load" event which fires
* when the Image has finished the network request and
* populated the Image with data
*/
image.addEventListener('load', () => {
/*
* You have to manually tell the Promise that you are
* done dealing with asynchronous stuff and you are ready
* for it to give anything that attached a callback
* through .then a realized value. We do that by calling
* resolve and passing it the realized value
*/
resolve(image);
});
/*
* Setting the Image.src is what starts the networking process
* to populate an image. After you set it, the browser fires
* a request to get the resource. We attached a load listener
* which will be called once the request finishes and we have
* image data
*/
image.src = url;
});
}
/*
* To use this we call the loadImage function and call .then
* on the Promise that it returns, passing a function that we
* want to receive the realized Image
*/
loadImage("example.com/house.jpg").then(houseImage => {
ctx.drawImage(houseImage, 0, 0);
});
In all honesty though, the loadImage function could be a little bit more robust since it doesn't handle errors right now. Consider the following enhancement:
const loadImage = (url) => new Promise((resolve, reject) => {
const img = new Image();
img.addEventListener('load', () => resolve(img));
img.addEventListener('error', (err) => reject(err));
img.src = url;
});
loadImage("example.com/house.jpg")
.then(img => console.log(`w: ${img.width} | h: ${img.height}`))
.catch(err => console.error(err));
A compressed/short example:
let url = "https://example.com/image.png";
let img = new Image();
await new Promise(r => {img.onload=r; img.src=url});
// now do something with img
I am making a easy html5 game.
Object.keys(gameConfig.playerElems).map((e) =>{
let img = gameConfig.playerElems[e];
let name = e;
let imgObj;
imgObj = new Image();
imgObj.src = img;
imgObj.onload = () => {
playerElemsCounter++;
drawPlayer(imgObj);
}
});
Is it possible to pause .map() iteration while imgObj will be loaded?
Is it possible to pause .map() iteration while imgObj will be loaded?
No. So instead, you use an asynchronous loop. Here's one example, see comments:
// A named IIFE
(function iteration(keys, index) {
// Get info for this iteration
let name = keys[index];
let img = gameConfig.playerElems[name];
let imgObj = new Image();
// Set event callbacks BEFORE setting src
imgObj.onload = () => {
playerElemsCounter++;
drawPlayer(imgObj);
next();
};
imgObj.onerror = next;
// Now set src
imgObj.src = img;
// Handles triggering the next iteration on load or error
function next() {
++index;
if (index < keys.length) {
iteration(keys, index);
}
}
})(Object.keys(gameConfig.playerElems), 0);
But, as Haroldo_OK points out, this will wait for one image to load before requesting the next, which is not only unnecessary, but harmful. Instead, request them all, draw them as you receive them, and then continue. You might do that by giving yourself a loading function returning a promise:
const loadImage = src => new Promise((resolve, reject) => {
const imgObj = new Image();
// Set event callbacks BEFORE setting src
imgObj.onload = () => { resolve(imgObj); };
imgObj.onerror = reject;
// Now set src
imgObj.src = src;
});
Then:
// Load in parallel, draw as we receive them
Promise.all(Object.keys(gameConfig.playerElems).map(
key => loadImage(gameConfig.playerElems[key])
.then(drawPlayer)
.catch(() => drawPlayer(/*...placeholder image URL...*/))
)
.then(() => {
// All done, if you want to do something here
});
// No need for `.catch`, we handled errors inline
If you wanted (for some reason) to hold up loading the next image while waiting for the previous, that loadImage function could be used differently to do so, for instance with the classic promise reduce pattern:
// Sequential (probably not a good idea)
Object.keys(gameConfig.playerElems).reduce(
(p, key) => p.then(() =>
loadImage(gameConfig.playerElems[key])
.then(drawPlayer)
.catch(() => drawPlayer(/*...placeholder image URL...*/))
)
,
Promise.resolve()
)
.then(() => {
// All done, if you want to do something here
});
// No need for `.catch`, we handled errors inline
...or with ES2017 async/await:
// Sequential (probably not a good idea)
(async function() {
for (const key of Object.keys(gameConfig.playerElems)) {
try {
const imgObj = await loadImage(gameConfig.playerElems[name]);
playerElemsCounter++;
drawPlayer(imgObj);
} catch (err) {
// use placeholder
drawPlayer(/*...placeholder image URL...*/);
}
}
})().then(() => {
// All done
});
// No need for `.catch`, we handled errors inline
Side note: There's no point to using map if you're not A) Returning a value from the callback to use to fill the new array map creates, and B) Using the array map returns. When you're not doing that, just use forEach (or a for or for-of loop).
The following function takes and image from an url, loads it, and returns its width and height:
function getImageData (url) {
const img = new Image()
img.addEventListener('load', function () {
return { width: this.naturalWidth, height: this.naturalHeight }
})
img.src = url
}
The problem is, if I do something like this:
ready () {
console.log(getImageData(this.url))
}
I get undefined because the function runs but the imaged hasn't loaded yet.
How to use await/async to return the value only when the photo has loaded and the width and height is already available?
How to use async/await to turn this callback function into a promise?
You don't. As usual, you use the new Promise constructor. There's no syntactic sugar for that.
function loadImage(url) {
return new Promise((resolve, reject) => {
const img = new Image();
img.addEventListener('load', () => resolve(img));
img.addEventListener('error', reject); // don't forget this one
img.src = url;
});
}
How to use await/async to log the value only when the photo has loaded and the width and height is already available?
You can do
async function getImageData(url) {
const img = await loadImage(url);
return { width: img.naturalWidth, height: img.naturalHeight };
}
async function ready() {
console.log(await getImageData(this.url))
}
This library works pretty well - it allows connection to the child process or simply returns the result asynchronously if desired: https://github.com/expo/spawn-async