-1

Desired result: I want that the images (image and image2) should load one-by-one. Following is my code:

'use strict';

window.addEventListener('DOMContentLoaded', () => {
  const cvs = document.querySelector('#cvs');
  cvs.width = window.innerWidth;
  cvs.height = window.innerHeight;
  const c = cvs.getContext('2d');

  let image = new Image(); // first image
  image.src = 'https://images5.alphacoders.com/559/559956.jpg';
  let image2 = new Image(); // second image
  image2.src = 'https://www.highreshdwallpapers.com/wp-content/uploads/2013/03/Avengers-A.jpg';
  let y = 0;

  let loader = img => new Promise(resolve => img.onload = resolve(img, 0, y, 100, 60));
  let logger = img => new Promise(resolve => img.onload = resolve(img.src + ' loaded!'));

  async function loadImages() {
    await logger(image).then(console.log); // this works
    await logger(image2).then(console.log); // this works

    await loader(image).then(c.drawImage);
    y += 60;
    await loader(image2).then(c.drawImage);
  };
  
  loadImages();
});
body {
  margin: 0;
}

#cvs {
  position: fixed;
}
<!DOCTYPE html>
<html>

<head>
  <title>Page Title</title>
  <link rel="stylesheet" type="text/css" href="./style.css">
</head>

<body>
  <canvas id='cvs'>Canvas not supported</canvas>
  <script type="text/javascript" src='./script.js'></script>
</body>

</html>

Here's my concept:

  • I create a function that returns a new Promise which resolves when the image loads and then draws it on the canvas context:
    let loader = img => new Promise(resolve => img.onload = resolve(img, 0, height, 100, 60));
    

  • Then, I create an asynchronous function that calls the loader for every image and draws it on canvas:
    async function loadImages() {
        // ...
        await loader(image).then(c.drawImage);
        y += 60;
        await loader(image2).then(c.drawImage);
    };
    

    But, for some reasons, the code isn't working. And, I get the following error: Uncaught (in promise) TypeError: Illegal invocation.

    I've tried replacing c.drawImage with:

  • c.drawImage.bind(c), as recommended in this post and,

  • c.drawImage.call, in this case, I've changed my resolve as resolve(c, img, 0, height, 100, 60)

    But, neither of them worked out!

    What I am doing wrong?

  • JJJ
    • 31,545
    • 20
    • 84
    • 99
    vrintle
    • 5,129
    • 1
    • 9
    • 39

    1 Answers1

    1

    Your loadImages returns undefined (since you don't return anything explicitly), so drawImage won't do anything.

    Even though it's not entirely clear what you wish to do, I have the feeling that you wanted to draw both images in this then().

    In order to do as little modifications in your code as possible, you could just return both images in an Array, then in the then iterate over this array and call drawImage on it.
    You'd also have to pass all your arguments as an array in the loader function.

    Also, note that you had a typo in img.onload = resolve(.., it should be img.onload = e => resolve(..., otherwise resolve gets called directly.

    And also remember that the onload event will fire only once, so once you awaited for it, it on't happen again (unless you force it to).

    window.addEventListener('DOMContentLoaded', () => {
      const cvs = document.querySelector('#cvs');
      cvs.width = window.innerWidth;
      cvs.height = window.innerHeight;
      const c = cvs.getContext('2d');
    
      let image = new Image(); // first image
      image.src = 'https://images5.alphacoders.com/559/559956.jpg';
      let image2 = new Image(); // second image
      image2.src = 'https://www.highreshdwallpapers.com/wp-content/uploads/2013/03/Avengers-A.jpg';
      let height = 0;
    
      let loader = img => new Promise(resolve => {
        // resolve the arguments as an Array
        img.onload = e => resolve([img, 0, height, 100, 60]);
        // force resetting the src, otherwise onload may already have fired
        img.src = img.src;
      });
    
      async function loadImages() {
        const a = await loader(image);
        height += 60;
        const b = await loader(image2);
        // you must return something if you it to be passed in the then()
        return [a, b];
      };
    
      loadImages().then(arr => {
        arr.forEach(args => c.drawImage.apply(c, args));
      }).catch(console.error);
    });
    <canvas id='cvs'>Canvas not supported</canvas>

    But if I may, your code is far from being clear... You'd probably win by being more verbose, and split your logic differently:
    First deal with loading the assets, then deal with your rendering objects (here the arguments you pass to drawImage).

    Mixing both will just make your code harder to maintain.

    Also, your variable named height would probably be better called y.

    Kaiido
    • 87,051
    • 7
    • 143
    • 194
    • No, the images are not drawn, just some scrollbars came on x and y axis – vrintle Nov 10 '18 at 08:32
    • I've updated my question in order to clear my logic a bit. The code logs in the console but doesn't draws the image. Why is it so? – vrintle Nov 10 '18 at 08:41
    • Sorry There was a typo I didn't spotted (onload = e =>) + Chrome fires the onload event of the second image before you do attach its handler since we await for the first image to have loaded before adding the second event handler. – Kaiido Nov 10 '18 at 09:14