1
\$\begingroup\$

I have the following 4 functions that work, however, I am sure that utilising promises will improve my code. I just find them so confusing.

$scope.takePictures = function() {

    document.addEventListener("deviceready", function() {

        var options = {
            quality: 75,
            destinationType: Camera.DestinationType.DATA_URL,
            sourceType: Camera.PictureSourceType.CAMERA,
            allowEdit: true,
            encodingType: Camera.EncodingType.JPEG,
            targetWidth: 200,
            targetHeight: 200,
            popoverOptions: CameraPopoverOptions,
            saveToPhotoAlbum: true
        };

        getImagesFromDevice(options);

    }, false);

}

$scope.getPictures = function() {

    var options = {
        quality: 50,
        destinationType: Camera.DestinationType.DATA_URL,
        sourceType: Camera.PictureSourceType.PHOTOLIBRARY,
        targetWidth: 200,
        targetHeight: 200
    };

    getImagesFromDevice(options);
}

var getImagesFromDevice = function(options) {
    $cordovaCamera.getPicture(options).then(function(imageData) {

        saveFileToParse(imageData);

    }, function(err) {
        // error
    });
}

var saveFileToParse = function(imageData) {
    var parseFile = new Parse.File("mypic.jpg", {
        base64: imageData
    });
    parseFile.save().then(function(response) {
        // The file has been saved to Parse.
        console.log(response._url);

        $scope.activeExercise.images.push(response._url);
        $scope.$apply();
        $scope.customImage = true;

    }, function(error) {
        // The file either could not be read, or could not be saved to Parse.
    });
}

It would be fantastic if someone can improve the above code but further explain the pitfalls in how I have coded it and how it can be improved. If you are able to force a promise explanation in my head that would be great too!

\$\endgroup\$
2
  • 1
    \$\begingroup\$ FYI, there's no guarantee that someone will come along and explain promises, especially if promises aren't the best way to go about this (I don't know much JavaScript though - could very well be the case, I don't know) \$\endgroup\$ – Mathieu Guindon Jun 24 '15 at 17:42
  • 1
    \$\begingroup\$ Parse.com on promises \$\endgroup\$ – Mast Jun 24 '15 at 17:58
1
\$\begingroup\$

Promises were meant to provide a linear appearance of async code instead of callbacks, as well as make it appear that the control is in one place rather than handing it off to other functions.

Also, functional programming is also of help. Not to the strict sense though, but trying to avoid side effects as much as possible as well as isolating code that mutates stuff to a few known locations can help you reason about your code.

// Let's just move out the handler so both getPictures and takePictures
// look the same.
$scope.takePictures = function () {
  document.addEventListener("deviceready", _takePictures, false);
}

$scope.getPictures = function () {
  // Taking this as an example, we call an async operation
  getImagesFromDevice({
    quality: 50,
    destinationType: Camera.DestinationType.DATA_URL,
    sourceType: Camera.PictureSourceType.PHOTOLIBRARY,
    targetWidth: 200,
    targetHeight: 200
  }).then(function(imageData){
    // When the operation succeeds, this function is called. The nice thing
    // about promises is that if you return a promise, the next chained
    // then listens to that promise instead of the original.
    return saveFileToParse(imageData);
  }).then(function(response){
    // What the above means is that this function now listens to
    // saveFileToParse instead of getImagesFromDevice
    //
    // Also notice that we isolate mutation to the scope to only here
    // and removed it from the other functions.
    $scope.activeExercise.images.push(response._url);
    $scope.$apply();
    $scope.customImage = true;
  });

  // In the end, your operation looks linear
}

function _takePictures() (
  getImagesFromDevice({
    quality: 75,
    destinationType: Camera.DestinationType.DATA_URL,
    sourceType: Camera.PictureSourceType.CAMERA,
    allowEdit: true,
    encodingType: Camera.EncodingType.JPEG,
    targetWidth: 200,
    targetHeight: 200,
    popoverOptions: CameraPopoverOptions,
    saveToPhotoAlbum: true
  }).then(function(imageData){
    return saveFileToParse(imageData);
  }).then(function(response){
    $scope.activeExercise.images.push(response._url);
    $scope.$apply();
    $scope.customImage = true;
  });
}

// These operations do not concern what you're doing with the data. All
// they know is that they receive options, operate, and hand you back
// the data when done. Pretty functional don't you agree?

// Another perk about promises is when you don't return a promise in the
// sequence, all chained thens will resolve using the same value. This
// allows us to chain a then for logging in between without affecting
// the result.

var getImagesFromDevice = function (options) {
  return $cordovaCamera.getPicture(options).then(function (imageData) {
    // logger for success
  }, function (err) {
    // logger for error
  });
}

var saveFileToParse = function (imageData) {
  return (new Parse.File("mypic.jpg", {
    base64: imageData
  })).save().then(function (response) {
    // logger for success
  }, function (error) {
    // logger for error
  });
}

Now if I were to write all this code synchronously, which is how a normal person would go about imperative programming (commands one after the other), the code appears 95% the same.

$scope.takePictures = function () {
  document.addEventListener("deviceready", _takePictures, false);
}

$scope.getPictures = function () {
  var imageData = getImagesFromDevice({
    quality: 50,
    destinationType: Camera.DestinationType.DATA_URL,
    sourceType: Camera.PictureSourceType.PHOTOLIBRARY,
    targetWidth: 200,
    targetHeight: 200
  });

  var response = saveFileToParse(imageData);

  $scope.activeExercise.images.push(response._url);
  $scope.$apply();
  $scope.customImage = true;

}

function _takePictures() (
  var imageData = getImagesFromDevice({
    quality: 75,
    destinationType: Camera.DestinationType.DATA_URL,
    sourceType: Camera.PictureSourceType.CAMERA,
    allowEdit: true,
    encodingType: Camera.EncodingType.JPEG,
    targetWidth: 200,
    targetHeight: 200,
    popoverOptions: CameraPopoverOptions,
    saveToPhotoAlbum: true
  });

  var response = saveFileToParse(imageData);

  $scope.activeExercise.images.push(response._url);
  $scope.$apply();
  $scope.customImage = true;

}

var getImagesFromDevice = function (options) {
  var imageData $cordovaCamera.getPicture(options);
  imageData ? /* success logger */ : /* error logger */;
  return imageData;
}

var saveFileToParse = function (imageData) {
  var response = (new Parse.File("mypic.jpg", { base64: imageData })).save();
  response ? /* success logger */ : /* error logger */;
  return response;
}

Now I'm assuming here that you use a "standard" promise. Angular's $q has a weird quirk where if you don't return anything from the resolve or reject handler, instead of resolving or rejecting with the same value from the original promise, it resolves regardless.

The fix is to manually return data or a reject.

var getImagesFromDevice = function (options) {
  return $cordovaCamera.getPicture(options).then(function (imageData) {
    // logger for success
    return imageData;
  }, function (err) {
    // logger for error
    return $q.reject(err);
  });
}
\$\endgroup\$
2
  • \$\begingroup\$ Absolutely fantastic - exactly what I was looking for! Thank you very much! Can you explain what the _ is about in _takePictures() obviously relates to the deviceReady however why _? \$\endgroup\$ – Taylorsuk Jun 26 '15 at 16:00
  • 1
    \$\begingroup\$ @Taylorsuk It's just a convention for marking "private" or "internal use only" functions. Also makes it easy for finding private functions in IDE's with code completion, just start with _. \$\endgroup\$ – Joseph Jun 26 '15 at 16:04

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.