loading an image on web browser using Promise()

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 = "https://stackoverflow.com/questions/52059596/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("https://stackoverflow.com/questions/52059596/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("https://stackoverflow.com/questions/52059596/example.com/house.jpg")
  .then(img => console.log(`w: ${img.width} | h: ${img.height}`))
  .catch(err => console.error(err));

Leave a Comment