Correct way to handle non-blocking image processing

I have an JavaScript extension that in short: upon user action, converts an image into a dataURI (and then does some processing)

Problem:
My issue is that using callbacks becomes unpleasant to deal with if more processing is required, for example, scaling, modifying, etc, where I use similar functions (load into a canvas, return result via callback) as they become nested and so on. It’d be better to keep things inline.

Question:
I’d like to use await image.decode(), instead of image.onload => () {}, but I’m afraid if I use await, it’ll block the rest of the page, waiting for it to load.

I’ve read up on many an article and SO question, and I thought I understood asynchronous calls, but I’d just like to be certain.

Or should I even be using Promises?

Here’s what I’ve used so far –

function getImageDataURL(imageSource, callback){
  let image = new Image();
  let cans = document.createElement('canvas');
  
  image.onload = function() {
    // processing 
    cans.width = this.naturalWidth;
    cans.height = this.naturalHeight;
    cans.getContext('2d').drawImage(this, 0,0);
    let q = 1
    while (cans.toDataURL("image/webp", q).length > 7500 && q != 0) {
      q -= 0.05; // Decreased quality increment size
    }
    callback(cans.toDataURL("image/webp", q));
    cans.remove(); // Remove Element once complete
  }
  image.src = imageSource;
}

And I understand when the image load starts, it releases control of the execution to the rest of the page.
Which is good, as the user experience isn’t deteriorated.

If I did:

async function asyncGetImageDataUrl(source) {
   ...
   image.src = source
   await image.decode()

   ... 
   return dataURI
}

It appears to behave the same way in my testing, but is it? Or does it become blocking

Final question:
Would the result be any different, if I called result = await asyncGetImage(src)?
Perhaps in an instance where I would want it to be blocking.

Added Context:
My usage might look like:

source = getImageSource(..)
if (source == foo) {
   doThis()
}
getImageURL(source, (result) => {
   if (result.length < max) {
      store(result)
   }
   else {
      moreImageProcessing(result, (processedResult) => {
         ...
      }
   }
}

And you can see how the nesting occurs with the initial pattern, and becomes messy.