Search code examples
javascriptangularjsangulares6-promise

How to convert method created to return a promise with $q library to use an ES6 Promise. Angularjs app to Angular4+


Since the ES6 Promise does not have a deferred object confused on how to go about converting this to work with ES6 Promises. One solution that I was looking at is to add a deffered object manually to the Promise Constructor. see the No deffered section for the example code

Reason: I am converting the angularjs app to angular4 and using it to better understand how to work with ES6 Promises. I was going to add the deferred object, but thought that was too much of a workaround.

 function generateImages() {
        console.log('generateImagesCalled')

        // set up promises
        var fullDeferred = $q.defer();
        var thumbDeferred = $q.defer();

        var resolveFullBlob = blob => fullDeferred.resolve(blob);
        var resolveThumbBlob = blob => thumbDeferred.resolve(blob);

        var displayPicture = (url) => {
            var image = new Image();
            image.src = url;

            // Generate thumb
            var maxThumbDimension = THUMB_IMAGE_SPECS.maxDimension;
            var thumbCanvas = _getScaledCanvas(image, maxThumbDimension);
            thumbCanvas.toBlob(resolveThumbBlob, 'image/jpeg', THUMB_IMAGE_SPECS.quality);

            // Generate full
            var maxFullDimension = FULL_IMAGE_SPECS.maxDimension;
            var fullCanvas = _getScaledCanvas(image, maxFullDimension);
            fullCanvas.toBlob(resolveFullBlob, 'image/jpeg', FULL_IMAGE_SPECS.quality);
        }

        var reader = new FileReader();
        reader.onload = (e) => {
            displayPicture(e.target.result);
        }
        reader.readAsDataURL(vm.currentFile);


        return $q.all([fullDeferred.promise, thumbDeferred.promise]).then(results => {
            console.log(results);
            return {
                full: results[0],
                thumb: results[1]
            }
        });

    }

Solution

  • In some special cases resolve and reject can be exposed in order to replicate deferreds:

    let pResolve;
    let pReject;
    const p = new Promise((resolve, reject) => {
      pResolve = resolve;
      pReject = reject;
    });
    

    Or deferred object can be formed:

    const deferred = {};
    deferred.promise = new Promise((resolve, reject) => {
        Object.assign(deferred, { resolve, reject });
    });
    

    Since deferreds aren't Promise built-in feature and are prone to be antipattern, it's preferable to solve this with constructor function only, when it is applicable.

    In the code above displayPicture and the fact that promises cannot be formed instantly but on load event limits the ways the situation can be efficiently handled. It can be improved with sticking to promises for events (also beneficial for further conversion to async..await):

        const readerPromise = new Promise(resolve => reader.onload = resolve);
        reader.readAsDataURL(vm.currentFile);
    
        const blobPromises = readerPromise.then(e => {
          const url = e.target.result;
    
          const fullBlobPromise = new Promise(resolve => {
            ...
            fullCanvas.toBlob(resolve, ...);
          });
    
          const thumbBlobPromise = ...;
    
          return Promise.all([fullBlobPromise, thumbBlobPromise]);
        });
    

    The promises should also make use of Angular digests in order to provide same control flow. Since promise internals don't rely on Angular services, there can be a single digest at the end:

        return blobPromises.then(
          () => $rootScope.$apply(),
          err => {
            $rootScope.$apply();
            throw err;
          }
        );
        // or
        return $q.resolve(blobPromises);