loading an image on web browser using Promise() - javascript

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

Related

Is an async function without await keyword equivalent to a function returning a Promise? [duplicate]

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

JavaScript Onload property

I have read some posts regarding the onload property. My understanding is that it is not a method, not an event, event listener, or trigger. It is merely a empty pointer to a function waiting to be assigned. Here is a shortened script. It assigns a image manipulation function upon event then renders the image to the web page. Instead of assign the function to onload which is the pointer to the function. Why can't I just execute the function directly? Am I making any sense here? Thanks
var reader = new FileReader();
reader.onload = function(e) {
var img = document.createElement("img");
img.src = e.target.result;
var canvas = document.createElement("canvas");
......
dataurl = canvas.toDataURL(file.type);
document.getElementById('output').src = dataurl;
}
reader.readAsDataURL(file);
Accordingly to MDN:
The FileReader.onload property contains an event handler executed when
the load event is fired, when content read with readAsArrayBuffer,
readAsBinaryString, readAsDataURL or readAsText is available.
So it's executed after reading content is done, that you're performing by calling reader.readAsDataURL(file).
onload is a field of the object FileReader which handles a reference to the function that you wanna execute when the desired file gets loaded. Essentially, it's the callback that gets called when the event load is triggered. Why using this pattern and not executing the function directly? Because loading files is an asynchronous task (which is initialized with readAsDataURL, readAsBinaryString, readAsArrayBuffer or readAsText).
You can't call the function directly because files load asynchronously. You can convert the FileReader code to return a promise though and then you can use async/await to make the code look like you're directly calling it
funciton readFileAsDataURL(file) {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onload = () => resolve(e.target.result);
reader.onerror = reject;
reader.readAsDataURL(file);
};
}
function loadImage(url) {
return new Promise((resolve, reject) => {
const img = new Image():
img.onload = () => resolve(img);
img.onerror = reject;
img.src = url;
};
}
async function foo() {
const result = await readFileAsDataURL(file);
const img = await loadImage(result);
// you can now use img
......
}
So in essence, OnLoad is an event handler (or reference to the event handler) for FileReader when it is called to further processes data when FileReader is done loading? Is my interpretation correct?

How to call functions only after the completion of the previous

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();
}

Object keys map - how to pause iteration

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).

How to turn this callback into a promise using async/await?

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

Categories