how to actually use promises - jakob mattsson, fishbrain
TRANSCRIPT
jakobm.com
@jakobmattsson
I’m a coder first and foremost. I also help companies recruit coders, train coders and architect software. Sometimes I do technical due diligence and speak at conferences. !
Want more? Read my story or blog.
From the Swedish depths
Social coding
FishBrain is the fastest, easiest way to log and share your fishing.!Get instant updates from anglers on nearby lakes, rivers, and the coast.
Enough with the madness
Enough with the madness
The lack and/or misuse of promises
Promises 101*
*No, this is not a tutorial
getJSON({ url: '/somewhere/over/the/rainbow', success: function(result) { // deal with it } });
getJSON({ url: '/somewhere/over/the/rainbow', success: function(result) { // deal with it } });
rainbows.then(function(result) { // deal with it });
var rainbows = getJSON({ url: '/somewhere/over/the/rainbow' });
getJSON({ url: '/somewhere/over/the/rainbow', success: function(result) { // deal with it } });
rainbows.then(function(result) { // deal with it });
var rainbows = getJSON({ url: '/somewhere/over/the/rainbow' });
f(rainbows); // such preprocessing!
Why is that a good idea?
• Loosen up the coupling • Superior error handling • Simplified async
That’s enough 101
Promises are not new
Promises are not newhttp://gi
thub.com/kriskowal
/q
http://www.html5rocks.com/en/t
utorials/es6/promises
http://domenic.me/2012/10/14/youre-missing-the-point-of-promises
http://www.promisejs.org
https://github.com/bellbind/using-promise-q
https://github.com
/tildeio/rsvp.js
https://github.com/cujojs/when
They’ve even made it into ES6
They’ve even made it into ES6
Already implemented natively in
Firefox 30Chrome 33
So why are you not using them?
So why are you not using them?
How to draw an owl
1. Draw some circles
How to draw an owl
1. Draw some circles
2. Draw the rest of the fucking owl
How to draw an owl
We want to draw owls.
!
Not circles.
getJSON('story.json').then(function(story) { addHtmlToPage(story.heading); ! // Map our array of chapter urls to // an array of chapter json promises. // This makes sture they all download parallel. return story.chapterUrls.map(getJSON) .reduce(function(sequence, chapterPromise) { // Use reduce to chain the promises together, // adding content to the page for each chapter return sequence.then(function() { // Wait for everything in the sequence so far, // then wait for this chapter to arrive. return chapterPromise; }).then(function(chapter) { addHtmlToPage(chapter.html); }); }, Promise.resolve()); }).then(function() { addTextToPage('All done'); }).catch(function(err) { // catch any error that happened along the way addTextToPage("Argh, broken: " + err.message); }).then(function() { document.querySelector('.spinner').style.display = 'none'; });
As announced for ES6
That’s circles. !
Nice circles! !
Still circles.
browser.init({browserName:'chrome'}, function() { browser.get("http://admc.io/wd/test-pages/guinea-pig.html", function() { browser.title(function(err, title) { title.should.include('WD'); browser.elementById('i am a link', function(err, el) { browser.clickElement(el, function() { browser.eval("window.location.href", function(err, href) { href.should.include('guinea-pig2'); browser.quit(); }); }); }); }); }); });
Node.js WebDriver Before promises
browser .init({ browserName: 'chrome' }) .then(function() { return browser.get("http://admc.io/wd/test-pages/guinea-pig.html"); }) .then(function() { return browser.title(); }) .then(function(title) { title.should.include('WD'); return browser.elementById('i am a link'); }) .then(function(el) { return browser.clickElement(el); }) .then(function() { return browser.eval("window.location.href"); }) .then(function(href) { href.should.include('guinea-pig2'); }) .fin(function() { return browser.quit(); }) .done();
After promises
Used to be callback-hell.
!
Now it is then-hell.
These examples are just a different way
of doing async. !
It’s still uncomfortable. It’s still circles!
The point of promises:
!
Make async code as easy to write
as sync code
The point of promises:
1 Promises out: Always return promises - not callback
2 Promises in: Functions should accept promises as well as regular values
3 Promises between: Augment promises as you augment regular objects
Three requirements
Let me elaborate
Example - Testing a blog API
• Create a blog • Create two blog entries • Create some users • Create some comments from those
users, on the two posts • Request the stats for the blog and
check if the given number of entries and comments are correct
post('/blogs', { name: 'My blog' }, function(err, blog) { ! post(concatUrl('blogs', blog.id, 'entries'), { title: 'my first post’, body: 'Here is the text of my first post' }, function(err, entry1) { ! post(concatUrl('blogs', blog.id, 'entries'), { title: 'my second post’, body: 'I do not know what to write any more...' }, function(err, entry2) { ! post('/user', { name: 'john doe’ }, function(err, visitor1) { ! post('/user', { name: 'jane doe' }, function(err, visitor2) { ! post('/comments', { userId: visitor1.id, entryId: entry1.id, text: "well written dude" }, function(err, comment1) { ! post('/comments', { userId: visitor2.id, entryId: entry1.id, text: "like it!" }, function(err, comment2) { ! post('/comments', { userId: visitor2.id, entryId: entry2.id, text: "nah, crap" }, function(err, comment3) { ! get(concatUrl('blogs', blog.id), function(err, blogInfo) { assertEquals(blogInfo, { name: 'My blog', numberOfEntries: 2, numberOfComments: 3 }); }); }); }); }); }); }); }); }); });
https:// github.com/
jakobmattsson/ z-presentation/
blob/master/ promises-in-out/
1-naive.js
1
Note: without narration, this slide lacks a lot of context. Open the file above and read the
commented version for the full story.
post('/blogs', { name: 'My blog' }, function(err, blog) { ! var entryData = [{ title: 'my first post', body: 'Here is the text of my first post' }, { title: 'my second post', body: 'I do not know what to write any more...' }] ! async.forEach(entryData, function(entry, callback), { post(concatUrl('blogs', blog.id, 'entries'), entry, callback); }, function(err, entries) { ! var usernames = ['john doe', 'jane doe']; ! async.forEach(usernames, function(user, callback) { post('/user', { name: user }, callback); }, function(err, visitors) { ! var commentsData = [{ userId: visitor[0].id, entryId: entries[0].id, text: "well written dude" }, { userId: visitor[1].id, entryId: entries[0].id, text: "like it!" }, { userId: visitor[1].id, entryId: entries[1].id, text: "nah, crap" }]; ! async.forEach(commentsData, function(comment, callback) { post('/comments', comment, callback); }, function(err, comments) { ! get(concatUrl('blogs', blog.id), function(err, blogInfo) { ! assertEquals(blogInfo, { name: 'My blog', numberOfEntries: 2, numberOfComments: 3 }); }); }); }); }); });
https:// github.com/
jakobmattsson/ z-presentation/
blob/master/ promises-in-out/
2-async.js
2
Note: without narration, this slide lacks a lot of context. Open the file above and read the
commented version for the full story.
https:// github.com/
jakobmattsson/ z-presentation/
blob/master/ promises-in-out/
3-async-more-parallel.js
3
Note: without narration, this slide lacks a lot of context. Open the file above and read the
commented version for the full story.
post('/blogs', { name: 'My blog' }, function(err, blog) { ! async.parallel([ function(callback) { var entryData = [{ title: 'my first post', body: 'Here is the text of my first post' }, { title: 'my second post', body: 'I do not know what to write any more...' }]; async.forEach(entryData, function(entry, callback), { post(concatUrl('blogs', blog.id, 'entries'), entry, callback); }, callback); }, function(callback) { var usernames = ['john doe', 'jane doe’]; async.forEach(usernames, function(user, callback) { post('/user', { name: user }, callback); }, callback); } ], function(err, results) { ! var entries = results[0]; var visitors = results[1]; ! var commentsData = [{ userId: visitors[0].id, entryId: entries[0].id, text: "well written dude" }, { userId: visitors[1].id, entryId: entries[0].id, text: "like it!" }, { userId: visitors[1].id, entryId: entries[1].id, text: "nah, crap" }]; ! async.forEach(commentsData, function(comment, callback) { post('/comments', comment, callback); }, function(err, comments) { ! get(concatUrl('blogs', blog.id), function(err, blogInfo) { assertEquals(blogInfo, { name: 'My blog', numberOfEntries: 2, numberOfComments: 3 }); }); }); }); });
post('/blogs', { name: 'My blog' }).then(function(blog) { ! var visitor1 = post('/user', { name: 'john doe' }); ! var visitor2 = post('/user', { name: 'jane doe' }); ! var entry1 = post(concatUrl('blogs', blog.id, 'entries'), { title: 'my first post', body: 'Here is the text of my first post' }); ! var entry2 = post(concatUrl('blogs', blog.id, 'entries'), { title: 'my second post', body: 'I do not know what to write any more...' }); ! var comment1 = all(entry1, visitor1).then(function(e1, v1) { post('/comments', { userId: v1.id, entryId: e1.id, text: "well written dude" }); }); ! var comment2 = all(entry1, visitor2).then(function(e1, v2) { post('/comments', { userId: v2.id, entryId: e1.id, text: "like it!" }); }); ! var comment3 = all(entry2, visitor2).then(function(e2, v2) { post('/comments', { userId: v2.id, entryId: e2.id, text: "nah, crap" }); }); ! all(comment1, comment2, comment3).then(function() { get(concatUrl('blogs', blog.id)).then(function(blogInfo) { assertEquals(blogInfo, { name: 'My blog', numberOfEntries: 2, numberOfComments: 3 }); }); }); });
https:// github.com/
jakobmattsson/ z-presentation/
blob/master/ promises-in-out/
4-promises-convoluted.js
4
Note: without narration, this slide lacks a lot of context. Open the file above and read the
commented version for the full story.
var blog = post('/blogs', { name: 'My blog' }); !var entry1 = post(concatUrl('blogs', blog.get('id'), 'entries'), { title: 'my first post', body: 'Here is the text of my first post' }); !var entry2 = post(concatUrl('blogs', blog.get('id'), 'entries'), { title: 'my second post', body: 'I do not know what to write any more...' }); !var visitor1 = post('/user', { name: 'john doe' }); !var visitor2 = post('/user', { name: 'jane doe' }); !var comment1 = post('/comments', { userId: visitor1.get('id'), entryId: entry1.get('id'), text: "well written dude" }); !var comment2 = post('/comments', { userId: visitor2.get('id'), entryId: entry1.get('id'), text: "like it!" }); !var comment3 = post('/comments', { userId: visitor2.get('id'), entryId: entry2.get('id'), text: "nah, crap" }); !var allComments = [comment1, comment2, comment2]; !var blogInfoUrl = concatUrl('blogs', blog.get('id')); !var blogInfo = getAfter(blogInfoUrl, allComments); !assertEquals(blogInfo, { name: 'My blog', numberOfEntries: 2, numberOfComments: 3 });
https:// github.com/
jakobmattsson/ z-presentation/
blob/master/ promises-in-out/
5-promises-nice.js
5
Note: without narration, this slide lacks a lot of context. Open the file above and read the
commented version for the full story.
1 Promises out: Always return promises - not callback
2 Promises in: Functions should accept promises as well as regular values
3 Promises between: Augment promises as you augment regular objects
Three requirements
Augmenting objects is a sensitive topic
Extending prototypes !
vs !
wrapping objects
You’re writing Course of action
An app Do whatever you want
A library Do not fucking modify thegod damn prototypes
Complimentary decision matrix
Promises are already wrapped objects
_(names) .chain() .unique() .shuffle() .first(3) .value()
Chaining usually requires a method to ”unwrap” or repeated wrapping
u = _(names).unique() s = _(u).shuffle() f = _(s).first(3)
Promises already have a well-defined way of
unwrapping.
_(names) .unique() .shuffle() .first(3) .then(function(values) { // do stuff with values... })
If underscore/lodash wrapped promises
But they don’t
Enter !
Z
1 Deep resolution: Resolve any kind of object/array/promise/values/whatever
2 Make functions promise-friendly: Sync or async doesn’t matter; will accept promises
3 Augmentation for promises: jQuery/underscore/lodash-like extensions
What is Z?
Deep resolution
var data = { userId: visitor1.get('id'), entryId: entry1.get('id'), text: 'well written dude' }; !Z(data).then(function(result) { ! // result is now: { // userId: 123, // entryId: 456, // text: 'well written dude' // } !});
Takes any object and resolves all promises in it.
!
Like Q and Q.all, but deep
1
Promise-friendly functions
var post = function(url, data, callback) { // POSTs `data` to `url` and // then invokes `callback` }; !post = Z.bindAsync(post); !var comment1 = post('/comments', { userId: visitor1.get('id'), entryId: entry1.get('id'), text: 'well written dude' });
bindAsync creates a function that
takes promises as arguments and
returns a promise.
2
Promise-friendly functions
var add = function(x, y) { return x + y; }; !add = Z.bindSync(add); !var sum = add(v1.get('id'), e1.get('id')); !var comment1 = post('/comments', { userId: visitor1.get('id'), entryId: entry1.get('id'), text: 'well written dude', likes: sum });
2
bindSync does that same, for functions that are not async
Augmentation for promises
var commentData = get('/comments/42'); !var text = commentData.get('text'); !var lowerCased = text.then(function(text) { return text.toLowerCase(); });
3
Without augmentation
every operation has to be
wrapped in ”then”
Augmentation for promises
Z.mixin({ toLowerCase: function() { return this.value.toLowerCase(); } }); !var commentData = get('/comments/42'); !commentData.get('text').toLowerCase();
3
Z has mixin to solve this
!
Note that Z is not explicitly applied
to the promise
Augmentation for promises
Z.mixin(zUnderscore); Z.mixin(zBuiltins); !var comment = get('/comments/42'); !comment.get('text').toLowerCase().first(5);
3
There are prepared packages to mixin
entire libraries
1 Promises out: Always return promises - not callback
2 Promises in: Functions should accept promises as well as regular values
3 Promises between: Augment promises as you augment regular objects
Three requirements
Make async code as easy to write
as sync code
Enough with the madness
You don’t have to use Z to do these things
But FFS
#noandthenwww.jakobm.com @jakobmattsson
!
github.com/jakobmattsson/z-core
bit.ly/sthlmjs+-
Enough with the madness