Start Using Promises Today:
a Hands-On Guide

Dmitry Pashkevich

Lucid Software

Agenda

  • Definition
  • Problems solved
  • Further usage ideas

Disclaimer

  • Here: "Promise" == "Promise-like API"
  • Will use jQuery Deferreds
  • You already use jQuery!
  • Interchangeable with other impl.

Intro

Promise

  • A value in the future
  • Can be resolved or rejected (once)
  • Can subscribe to an already fulfilled promise

Example: load user info

// Load user information

$.get({
    url: userUrl,
    success: function(user) {
        $('.user-indicator').text(user.name);
    },
    error: function(xhr, reason) {
        growl("Something went wrong", reason);
    }
});
// Load user information

var promise = $.get({
    url: userUrl
});

promise.done(function(user) {
    $('.user-indicator').text(user.name);
}).fail(function(xhr, reason) {
    growl('Something went wrong', reason);
});

Problems with callback arguments

Problem #1

Where do I pass the callback?

/** WITHOUT PROMISES **/

// is there an error callback?
function mergeChanges(changes, callback, updateLastModified) {}

// what if I don't care to handle failure here?
function publish(format, DPI, 
    successCallback, failureCallback) {}

// callback argument placement is inconsistent
function generateShareLink(id, isDocument, multipleUse, 
    role, callback, folderOwnerId) {}

Problem #1

Where do I pass the callback?

/** WITH PROMISES **/

publish('png', 160).done(function(url) {
    console.log('Published!');
});

Problem #2

Need to add an error callback

/** WITHOUT PROMISES **/

function getUser(userId, callback, 
    useCache) {}
/** WITH PROMISES **/

function getUser(userId, useCache) {
    ...

    // added later
    if(!ok) {
        deferred.reject("Bummer!");
    }
    // /added later

    return deferred.promise();
}
// Now I need to handle errors!
// change all calls to this
function getUser(userId, onSuccess, 
    onError, useCache) {}

// OR keep an ugly signature
function getUser(userId, onSuccess, 
    useCache, onError) {}

Problem #3a

getDocumentFonts(function(fonts) {
    documentFontsLoaded = true;

    if(defaultFontsLoaded) {
        afterAllFontsLoaded();
    }
});

getDefaultFonts(function(fonts) {
   defaultFontsLoaded = true;

   if(documentFontsLoaded) {
        afterAllFontsLoaded();
   }
});

function afterAllFontsLoaded() {
    refreshOptionBar();
    refreshFontsList();
}
var docFontsPromise = 
        getDocumentFonts();
var defaultFontsPromise = 
        getDefaultFonts();

Compose async operations

// Load the document fonts
function getDocFonts(callback) {}

// Load the default fonts
function getDefaultFonts(callback) {}

/**
 * Problem: refresh the UI 
 * after all fonts are loaded
 */
// When everything has completed
$.when(
    docFontsPromise, 
    defaultFontsPromise
).done(function() {
    refreshOptionBar();
    refreshFontsList();
});

Problem #3b

function getUserPic() {
    // what if getUser becomes async?
    var user = this.getUser();

    return $.get(user.picUrl);
}



getUserPic.done(function(pic) {
    // display picture
}).fail(function(xhr, status, err) {
    console.error(err);
});

Compose async operations

function getUserPic() {
  return this.getUser()
          .done(function(user) {
            // chain promises
            return $.get(user.url); 
          };
}


getUserPic.done(function(pic) {
    // display picture
}).fail(function(xhr, status, err) {
    console.error(err);
});
  • Outside code doesn't change
  • Don't have to handle getUser() failure

Problem #3c

function refreshVersions() {
    $.get(versionsListUrl).done(function(
        var allImagesReady = [];  // will save promises here
        container.empty();

        result.forEach(function(version) {
            var img = $('<img>').append(container);
            var downloadPromise = $.get(version.downloadUrl)
                .done(function(result) {
                    img.attr('src', result['thumb']);
                });
            allImagesReady.push(downloadPromise);
        });

        $.when.apply($, allImagesReady).always(function() {
            updateHeight();
            var input = $('.input[value='+curVersion+']');
            prop('checked', true).focus();
        });
    });
}

Compose async operations

Benefits of Promises

  • A uniform interface for async operations
  • Cleanly separates the input arguments from control flow structures
  • Less coupling
  • Convenient error handling

Usage Ideas

Network requests



$.get(userResource.url).done(function(user) {
    $('.user-indicator').text(user.name);
}).fail(function(xhr, reason) {
    growl('Something went wrong', reason);
});

Better File API

// using YASMF framework
var fm = new _y.FileManager();
fm.init(fm.PERSISTENT, 1024 * 1024)
     .then(function() {
        return fm.readDirectoryContents (".", {}); 
     }) 
     .then(function(entries) {
        //process the entries 
     }) 
     .catch(function(err) {
        console.log(err.code); 
     });

Dialogs / Prompts

insertDialog.show(pageId).done(insertMacro)
    .fail(function(errMsg) {
        alert(errMsg);
    });
editConfirmDialog.show().done(openEditorFrame);
versionDialog.show().done(function(newVersion) {
    if (newVersion != curVersion) {
        macroParams['version'] = newVersion;
        updateMacro(macroParams);
    }
});

Web Animation API



box.animate([
    { opacity: 1 }, 
    { opacity: 0 }
], 1000).then(function() {
    box.remove();
});

Your ideas?

The Future...

Is Here!

 

ES6 Promises

Thanks!