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!
Start Using Promises Today: a Hands-On Guide (OpenWest 2016)
By dpashkevich
Start Using Promises Today: a Hands-On Guide (OpenWest 2016)
- 2,010