0

I nearly have the theory down but am getting stuck wit the fine details. An image sizing function, given a var $data (which is the event object of a file input after changing) will return a var dataurl back (which is a dataurl jpeg).

My issue was getting dataurl to recognise it was updated inside a nested function. The code, as I show it below, was logging data:image/jpg;base64 to the console, showing dataurl was not updated.

Research led me to find that this is due to the asynchronous calls and this makes sense. The var wasn't changed in time before moving on and printing to the console. I've also learned that this is a standard situation to issue a callback to update the var.

Having made a few attempts at doing so, I can't find a clean (or working) method to update the var using a callback. I'll expand on this after I show my current code:

function clProcessImages($data) {

        var filesToUpload = $data.target.files;
        var file = filesToUpload[0];
        var canvas = document.createElement('canvas');
        var dataurl = 'data:image/jpg;base64';

        var img = document.createElement("img");

        var reader = new FileReader();
        reader.onload = function(e, callback) {

            img.onload = function () {

                var ctx = canvas.getContext("2d");
                ctx.drawImage(img, 0, 0);

                var MAX_WIDTH = 800;
                var MAX_HEIGHT = 600;
                var width = img.width;
                var height = img.height;

                if (width > height) {
                    if (width > MAX_WIDTH) {
                        height *= MAX_WIDTH / width;
                        width = MAX_WIDTH;
                    }
                } else {
                    if (height > MAX_HEIGHT) {
                        width *= MAX_HEIGHT / height;
                        height = MAX_HEIGHT;
                    }
                }
                canvas.width = width;
                canvas.height = height;
                var ctx = canvas.getContext("2d");
                ctx.drawImage(img, 0, 0, width, height);
                dataurl = canvas.toDataURL("image/jpeg",0.8);
                callback(dataurl);

            }
            img.src = e.target.result

        }
        reader.readAsDataURL(file);

        console.log(dataurl); // console log mentioned in question
        return dataurl;

    };

This is the code as it stands, somewhere in the middle of figuring out how to pull off the callback. I'm aware that in its shown state the callback isn't called.

My first hurdle was that I (seemingly) couldn't later call the reader.onload function again, as it appears to be an event specifically to be fired when the object is loaded, not something to call on manually at any time. My best guess was to create a new function that reader.onload would trigger, would contain all of the the current reader.onload function and I could specify directly later, to issue the callback, skipping referring to the onload event.

Seemed like a sound theory, but then I got stuck with the event object that the onload function passes through. Suppose I apply the above theory and the onload event calls the new function readerOnLoad(e). It would pass through the event object fine. However, when wanting to perform a callback later, how could I manually call readerOnLoad(e) as I don't see a way to pass on the event object from the same FileReader in an elegant way. It feels a little like I'm straying into very messy code* to solve whereas it feels like there should be something a little bit more elegant for a situation that doesn't feel too niche.

*The original $data event object variable was obtained by storing a global variable when a file input detected a change. In theory, I could create a global variable to store the event object for the FileReader onload. It just feels quite convoluted, in that the difference between my original code and a code that functions this way is quite severe and inelegant when the only difference is updating a var that wasn't ready in time.

biscuitstack
  • 11,591
  • 1
  • 26
  • 41

1 Answers1

2

When working with asynchronous function, you would usually want to pass a function as callback. You can use NodeJS style callback like this:

// asyncTask is an asynchronous function that takes 3 arguments, the last one being a callback

asyncTask(arg1, arg2, function(err, result) {
  if (err) return handleError(err);
  handleResult(result);
})

So in your case, you can do:

clProcessImages($data, function(dataurl) {
  console.log(dataurl);
  // Do you other thing here
});

function clProcessImages($data, onProcessedCallback) {
  var filesToUpload = $data.target.files;
  var file = filesToUpload[0];
  var canvas = document.createElement('canvas');
  var dataurl = 'data:image/jpg;base64';

  var img = document.createElement("img");

  var reader = new FileReader();
  reader.onload = function(e) {

    img.onload = function () {

      var ctx = canvas.getContext("2d");
      ctx.drawImage(img, 0, 0);

      var MAX_WIDTH = 800;
      var MAX_HEIGHT = 600;
      var width = img.width;
      var height = img.height;

      if (width > height) {
        if (width > MAX_WIDTH) {
          height *= MAX_WIDTH / width;
          width = MAX_WIDTH;
        }
      } else {
        if (height > MAX_HEIGHT) {
          width *= MAX_HEIGHT / height;
          height = MAX_HEIGHT;
        }
      }
      canvas.width = width;
      canvas.height = height;
      var ctx = canvas.getContext("2d");
      ctx.drawImage(img, 0, 0, width, height);
      dataurl = canvas.toDataURL("image/jpeg",0.8);
      onProcessedCallback(dataurl);

    }
    img.src = e.target.result

  }
  reader.readAsDataURL(file);

  return dataurl;
};

You might also want to pass error object to the callback like the first example, and handle it appropriately.

In short, always add a callback function as an argument to your own asynchronous function.

dezull
  • 990
  • 1
  • 6
  • 18
  • works well and explanation makes sense. Thanks for that. The one peculiarity is that if I add `form_data.append('file', dataurl);` immediately after the `console.log` (which confirms it has updated correctly to contain the jpeg) my `PHP` handler says it's empty if I check `if(!file_exists($_FILES['file']['tmp_name']) || !is_uploaded_file($_FILES['file']['tmp_name']))`. Though everything else I appended to `form_data` is returned fine in the handler, suggesting the form was sent ok. – biscuitstack Apr 23 '17 at 14:21
  • Think I got it. Works when I convert the dataurl to a blob as per: http://stackoverflow.com/questions/4998908/convert-data-uri-to-file-then-append-to-formdata/5100158. Not sure why many posts elsewhere on this site suggest appending the dataurl as it is. – biscuitstack Apr 23 '17 at 14:39