1
\$\begingroup\$

Using parse.com's javascript API, I would like to get a variable number or images from img tags on a page and save those images in the currently logged in user's user object.

To my knowledge, parse cannot store image files directly from a url so I am using ajax to send each url to a php page that converts it and returns a base64 encoded string

Of course, left unchecked, the code would continue to run and save the user object before the php page returns the encoded images.

As a quick and dirty solution I set async: false which does work but of course give the warning: synchronous XMLHttpRequest on the main thread is deprecated because of its detrimental effects to the end user's experience.

How can I re-write the below to use promises instead of async: false?

Or, should I gather up all of the image urls, send them in one ajax call, encode each, send them all back, and move the save user code into the success function of the ajax call?

function accountCreation(){

        var user = Parse.User.current(); // get the currently logged in user object

        // loop through each image element
        $('.images').each(function(index, element) {
              // first we'll need to get the image source 
              var src = $(element).attr('src');
              // alone, the source is just a url and not of much use to us
              // we'll need to take the source and create a base64 encoded string from it
              // we'll use an ajax call to a small php script (shown below) to do this
              // it might be better to gather up each src url and send them all in one ajax call??
              var encodedImg;
              try {
                $.ajax({
                   type: "POST",
                   url: "encodeImgToBase64.php", // you may need to adjust this path depending on where you store the file in relation to this file
                   data: 'url='+src,
                   success: function(data){
                     encodedImg = data; // encodedImg is now a base64 encoded string 
                     // if I did send all the image urls at once and return all the encoded strings in one response, I guess I could just move the rest of the below code into this success function and be done with it
                   },
                   async: false 
                   // note the use of `async: false` here
                   // this works but gives the warning:
                   // synchronous XMLHttpRequest on the main thread is deprecated because of its detrimental effects to the end user's experience.
                   // I know I should be using a promise here but Im not sure how to incorporate it
                 });
                var file = new Parse.File("photo.jpg", { base64: encodedImg }); // this part actually saves the image file to parse
                user.set("image"+index,file); // set this image to the corosponding column on our object 
              } catch (e) {
                console.log('failed to create file, src= '+src+', encoded img='+encodedImg)
              }
        });
        // save the user object
        user.save(null,{
          success: function(user) { 
              // do success stuff...
          },
          error: function(user, error) {
              // do fail stuff...
          }
        });
 }

Here is the php code from encodeImgToBase64.php

<?php
if (isset($_POST['url']) ){
    echo base64_encode(file_get_contents($_POST['url']));
}else{
    echo 'Error: No image to encode';
}
?>
\$\endgroup\$
1
\$\begingroup\$

I don't know Parse() myself, but this should show you the general idea for using promises with jQuery Ajax:

function accountCreation(){
    var user = Parse.User.current(); // get the currently logged in user object

    // loop through each image element
    var promises = $('.images').map(function(index, element) {
        var src = $(element).attr('src');
        return $.ajax({
            type: "POST",
            url: "encodeImgToBase64.php", // you may need to adjust this path depending on where you store the file in relation to this file
            data: 'url='+src,
        });
    });
    $.when.apply($, promises).then(function() {
        // arguments[0][0] is first result
        // arguments[1][0] is second result and so on
        for (var i = 0; i < arguments.length; i++) {
            var file = new Parse.File("photo.jpg", { base64: arguments[i][0] }); // this part actually saves the image file to parse
            user.set("image" + i, file); // set this image to the corosponding column on our object 
        }
        // save the user object
        user.save(null,{
          success: function(user) { 
              // do success stuff...
          },
          error: function(user, error) {
              // do fail stuff...
          }
        });
    });
 }

Here's a description of how the code works:

$('.images').map() cycles through all the images, calling a custom function for each item. The return value from the custom callback is added to an array and the final result of the .map() call is an array of all the returned values.

return $.ajax() returns from the .map() callback, the promise that $.ajax() returns. So, when the .map() is done, it will have an array of ajax promises.

$.when.apply($, promises) passes the array of promises to $.when() which then waits until all the promises are resolved (e.g. all the ajax calls have completed) before calling the .then() handler.

In the .then() handler, the results of all the ajax calls are passed as arguments. Each argument is an array of length three. [0] in each array is the returned data from the ajax call. [1] is the status from the ajax call, [2] is the jqXHR object from the ajax call. In this case, you just need [0] because we already know the status.

So, then .then() handler cycles through all the arguments object passed to the .then() handler with one array passed as an argument for each ajax call that was executed and the [0] in each of those arrays is the ajax result.

Then, after processing all the ajax results, user.save() is called.


FYI, when looking at the Parse documentation, user.save() looks like it also returns a promise and new Parse.File() looks like it has a .save() method (not sure if that needs to be called).

\$\endgroup\$
  • \$\begingroup\$ Worked perfectly. No, it's not mentioned in the js guide but you dont have to explicitly call save() with new Parse.File(). At least you don't when used with base64 encoded images, not sure about other types of images Thank you! \$\endgroup\$ – DelightedD0D Mar 6 '15 at 21:19
  • \$\begingroup\$ So this is a different structure than what Ive been looking at, seems simpler. As I understand it, .map() loops the array of elements, for each element returning the encoded version of the image making promises an array of base64 encoded images. Meanwhile, $.when.apply($, promises).then() basically listens for promises to get it's value from the function (when the loop is complete) and when it does, the callback is called silently passing in arguments? \$\endgroup\$ – DelightedD0D Mar 6 '15 at 21:30
  • 1
    \$\begingroup\$ @DelightedD0D - I added a description of how the code works to the end of my answer. \$\endgroup\$ – jfriend00 Mar 7 '15 at 3:16

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.