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