Object keys map - how to pause iteration - javascript

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

Related

JS Promise wait for nested asynchronous function

I'm having an issue getting 2 asynchronous operations properly timed so the outer function waits for the inner function to finish before continuing. The solution I'm trying to implement is based on an existing solution which uses ES2015, so I can't use await and have to work with Promises.
The solution is split in multiple classes, which for an easy overview I call Main, Invoker and Inner. So the Main class uses an instance of the Invoker to asynchronously execute a command from the Inner class and should then wait for the response, which works but it doesn't wait for the asynchronous Image.onload() from the setSize() of the Inner class.
class Inner extends Component{
setSize(size){
const newImage = { url: null, imageName: canvasImage.imageName};
... some stuff...
const tempImage = new Image();
tempImage.src = canvas.toDataURL();
tempImage.onload = function(){
... some stuff...
const tempCanvas = document.createElement("canvas");
const tempCtx = tempCanvas.getContext("2d");
tempCtx.drawImage(image, x, y, xa, ya);
newImage.url = tempCanvas.toDataURL();
}
return Promise.resolve(newImage);
}
}
class Main{
resize(size){
imageData = this.execute(commandName,'setSize', size);
imageData.then(image => {
... do stuff with image data...
});
}
execute(commandName, ...args) {
return this._invoker.execute(commandName, ...args);
}
}
class Invoker{
execute(...args) {
let [command] = args;
if (isString(command)) {
command = commandFactory.create(...args);
}
return this._invokeExecution(command).then((value) => {
this.clearRedoStack();
return value;
});
}
}
I already tried to move the "return Promise.resolve(newImage) in the setSize Function inside the onload function, but then I'm getting an error in the invoker, because of the missing resolve.
Any ideas?
since the setSize function is using a callback based syntax with onload, you need to wait until the onload function is called by the browser,
you can return a promise from setSize and handle onload for resolve and onerror for reject, something like:
setSize(size){
return new Promise((res, rej) => {
const newImage = { url: null, imageName: canvasImage.imageName};
... some stuff...
const tempImage = new Image();
tempImage.src = canvas.toDataURL();
tempImage.onload = function(){
... some stuff...
const tempCanvas = document.createElement("canvas");
const tempCtx = tempCanvas.getContext("2d");
tempCtx.drawImage(image, x, y, xa, ya);
newImage.url = tempCanvas.toDataURL();
res(newImage);
}
tempImage.onerror = function(error) {
rej(error)
}
})
}
You need to return a promise and resolve it in the onload.
setSize () {
return new Promise((resolve, reject) => {
...
tempImage.onload = function () {
...
resolve(newimage)
}
})
}

How to write a preload function in Javascript?

How do I write a function where everything within the function gets loaded before another function is called? For example, (similar to P5.js) I want to implement a preload function that will load anything that needs to be loaded (ie Images) before executing a setup function.
let someImage;
function preload() {
someImage = new Image();
someImage.src = "some URL";
someImage.onload = imageLoaded;
}
function imageLoaded() {
//this is called before setup
}
function setup() {
//I can safely assume everything is properly loaded
}
To preload an image, you need to actually be careful what order you supply the values. If you add src before onload it is possible that the image will load before the onload function will run.
It is annoyingly more possible than it sounds. Same thing goes for <script> as well.
Here's how I would do it:
let someImage;
function preload() {
someImage = new Image();
return new Promise((resolve, reject) => {
// this will pass the event object in the .then function
someImage.onload = resolve;
// Optional if you want to handle images not loading
// someImage.onerror = reject;
someImage.src = "some URL";
});
}
// Now you can do it two ways depending on what floats your boat
// You can change your setup function to be async/await
async function setup() {
try {
let imageResult = await preload();
imageLoaded();
// code that would be in your original setup function
} catch (e) {
// handle error
}
}
// Or you can do promise chaining with callbacks
preload()
.then(loadEvent => imageLoaded())
.then(() => setup());
// .catch(error => handleError(error)); // Optional;
Making sure you assign the Event before the .src, you could execute a function .onload of your image....
function preload(url, doneFunc){
const img = new Image;
img.onload = ()=>{
doneFunc();
}
img.onerror = ()=>{
console.log(img.src+' load failure');
}
img.src = url;
}
preload('yourImg.png', ()=>{
console.log('execute code here');
});
.... or you could use a Promise, like:
function preload(url){
const img = new Image;
const p = new Promise((resolve,reject)=>{
img.onload = ()=>{
resolve();
}
img.onerror = ()=>{
reject(img.src+' load failure');
}
});
img.src = url;
return p;
}
preload('not_a_good_src.png').then(()=>{
console.log('execute code here');
}).catch(error=>{
console.log(error);
});
Of course, I don't recommend loading the images synchronously, when they can be loaded at the same time instead.
using async await you can do it
let someImage="i-m-a--g-e";
let load = true
function preload() {
console.log("running preload",someImage)
return new Promise((resolve,reject)=>{
if(load){ return resolve()}else{return reject()}
}
);
}
function setup(){
console.log("running setup")
}
(async () => {
await preload();
setup();
})();
JavaScript Demo: Promise.reject()
The best way to implement that in Javascript is to have your preload function return a promise, then put all of your code in the promise success return. If you want functions called in a certain order, then you can do a promise chain.
let someImage;
function preload() {
someImage = new Image();
someImage.src = "some URL";
someImage.onload = imageLoaded;
return new Promise((resolve)=>resolve());
}
preload().then(function(){
setup();
});
function setup() {
}
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise
https://javascript.info/promise-chaining

Create a promise with a dynamic timeout

I have created a Promise in oder to get the duration of one file whenever has finished its synthesising.
I believe that the solution is really inefficient, since I set a timeout regardless when the task has finished so probably I will just waste time each time I call the method:
polly.synthesizeSpeech(params, function (err, data) {
if (err)
console.log(err, err.stack);
else {
var uInt8Array = new Uint8Array(data.AudioStream);
var arrayBuffer = uInt8Array.buffer;
var blob = new Blob([arrayBuffer]);
var urlAudioFile = URL.createObjectURL(blob);
var audio = new Audio(urlAudioFile);
audio.type = 'audio/wav';
getAudioFileDurationAsync(audio);
};
});
function getAudioFileDurationAsync(audio) {
let promise = new Promise(function (resolve, reject) {
setTimeout(() => {
resolve("done!")
}, 3000);
});
promise.then(
result => {
console.log(audio.duration);
},
error => console.log(error) // doesn't run
);
};
Obviously, after 3000ms I get the duration of the file, but I would like to do it as soon as the file has finished synthesising. How could I do it?
From the documentation, it seems to be possible to get the duration :
var audioElement = new Audio('car_horn.wav');
audioElement.addEventListener('loadeddata', () => {
let duration = audioElement.duration;
// The duration variable now holds the duration (in seconds) of the audio clip
})
Hope it helps you
is that working for you?
Basically you just need to wrap the code you want to be notified about with a Promise. If you have a callback function just like in your example, all you have to do is let it resolve from within that callback.
const audioFileDuration = (params) => new Promise((resolve, reject) => {
polly.synthesizeSpeech(params, function(err, data) {
if (err) {
reject(err);
}
var uInt8Array = new Uint8Array(data.AudioStream);
var arrayBuffer = uInt8Array.buffer;
var blob = new Blob([arrayBuffer]);
var urlAudioFile = URL.createObjectURL(blob);
var audio = new Audio(urlAudioFile);
audio.type = 'audio/wav';
resolve(audio.duration)
});
});
audioFileDuration(params).then(duration => console.log(duration))
SetTimeOut acts as the maximum duration[TTL] you want to wait for the function.
You can try resolving the promise in two flows
when timeout reached of 3000ms
as soon as you get the duration of file after it has finished its synthesising.
whichever of the above flows completes early will resolve the promise and your code could proceed further not waiting for second resolve.
Make sure you clear timeout if the second process(getting duration) is finished earlier

Attach DOM elements to other DOM elements only after they have loaded

I am trying to write the code to do the following:
When the user uploads the images, return previews of those images as canvas elements. (This is taken care of by loadImage API)
Attach div elements next to each of those canvases.
To do the above in order, I will need to invoke document.querySelectorAll method only after the canvas elements are fully drawn.
Here is the code I wrote. I tried to use promise here, but apparently the promise is resolved after the loadImage functions are invoked, not after the canvas elements are fully drawn. When I run the function, the previews are populated as canvas elements, but the 'photo_order' div elements are not attached.
function rpu() {
let promise = new Promise(async function(resolve, reject) {
let filez = e.target.files;
let files = Array.from(filez);
files.forEach(function(file) {
loadImage(
file,
function(img) {
document.querySelector('#r_im_preview').appendChild(img);
document.querySelector('#r_im_preview').classList.remove('hide');
}, {
maxWidth: 150,
orientation: true,
contain: true
}
)
});
resolve();
});
promise.then(function() {
let k1 = document.querySelectorAll('canvas');
let k2 = Array.from(k1);
let k3 = '<div class="photo_order" style="cursor: pointer;"></div>'
k2.forEach(k => {
k.insertAdjacentHTML('afterEnd', k3);
})
});
}
How do I fix the code to achieve what I am trying to do? Any advice will be very much welcome.
I think you need to refactor your code to correctly use promises. Something like this:
const appendPreview = (img) => {
document.querySelector('#r_im_preview').appendChild(img)
document.querySelector('#r_im_preview').classList.remove('hide');
}
const loadImageP = (file, cb, settings) => new Promise((resolve, reject) => {
try {
const cbP = (...args) => (cb(...args), resolve)
loadImage(file, cbP, settings)
} catch {
reject()
}
})
const settings = {
maxWidth: 150
orientation: true,
contain: true
}
async function rpu() {
const promises = [...e.target.files].reduce((acc, file) =>
[...acc, loadImageP(file, appendPreview, settings))], [])
await Promise.all(promises)
let canvasNodes = document.querySelectorAll('canvas');
let orderNode = '<div class="photo_order" style="cursor: pointer;"></div>'
[...canvasNodes].forEach((n) => { n.insertAdjacentHTML('afterEnd', orderNode);
}

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

Categories