how to actually use promises - jakob mattsson, fishbrain

66
jakobm.com @jakobmattsson I’m a coder first and foremost. I also help companies recruit coders, train coders and architect soware. Sometimes I do technical due diligence and speak at conferences. Want more? Read my story or blog.

Upload: codemotion-tel-aviv

Post on 15-Jul-2015

238 views

Category:

Technology


0 download

TRANSCRIPT

Page 1: How to actually use promises - Jakob Mattsson, FishBrain

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.

Page 2: How to actually use promises - Jakob Mattsson, FishBrain

From the Swedish depths

Page 3: How to actually use promises - Jakob Mattsson, FishBrain

Social coding

Page 4: How to actually use promises - Jakob Mattsson, FishBrain

FishBrain is the fastest, easiest way to log and share your fishing.!Get instant updates from anglers on nearby lakes, rivers, and the coast.

Page 5: How to actually use promises - Jakob Mattsson, FishBrain

Enough with the madness

Page 6: How to actually use promises - Jakob Mattsson, FishBrain

Enough with the madness

The lack and/or misuse of promises

Page 7: How to actually use promises - Jakob Mattsson, FishBrain

Promises 101*

*No, this is not a tutorial

Page 8: How to actually use promises - Jakob Mattsson, FishBrain

getJSON({ url: '/somewhere/over/the/rainbow', success: function(result) { // deal with it } });

Page 9: How to actually use promises - Jakob Mattsson, FishBrain

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' });

Page 10: How to actually use promises - Jakob Mattsson, FishBrain

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!

Page 11: How to actually use promises - Jakob Mattsson, FishBrain

Why is that a good idea?

• Loosen up the coupling • Superior error handling • Simplified async

Page 12: How to actually use promises - Jakob Mattsson, FishBrain

That’s enough 101

Page 13: How to actually use promises - Jakob Mattsson, FishBrain

Promises are not new

Page 14: How to actually use promises - Jakob Mattsson, FishBrain

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

Page 15: How to actually use promises - Jakob Mattsson, FishBrain

They’ve even made it into ES6

Page 16: How to actually use promises - Jakob Mattsson, FishBrain

They’ve even made it into ES6

Already implemented natively in

Firefox 30Chrome 33

Page 17: How to actually use promises - Jakob Mattsson, FishBrain

So why are you not using them?

Page 18: How to actually use promises - Jakob Mattsson, FishBrain

So why are you not using them?

Page 19: How to actually use promises - Jakob Mattsson, FishBrain

How to draw an owl

Page 20: How to actually use promises - Jakob Mattsson, FishBrain

1. Draw some circles

How to draw an owl

Page 21: How to actually use promises - Jakob Mattsson, FishBrain

1. Draw some circles

2. Draw the rest of the fucking owl

How to draw an owl

Page 22: How to actually use promises - Jakob Mattsson, FishBrain

We want to draw owls.

!

Not circles.

Page 23: How to actually use promises - Jakob Mattsson, FishBrain

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

Page 24: How to actually use promises - Jakob Mattsson, FishBrain

That’s circles. !

Nice circles! !

Still circles.

Page 25: How to actually use promises - Jakob Mattsson, FishBrain

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

Page 26: How to actually use promises - Jakob Mattsson, FishBrain

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

Page 27: How to actually use promises - Jakob Mattsson, FishBrain

Used to be callback-hell.

!

Now it is then-hell.

Page 28: How to actually use promises - Jakob Mattsson, FishBrain
Page 29: How to actually use promises - Jakob Mattsson, FishBrain
Page 30: How to actually use promises - Jakob Mattsson, FishBrain

These examples are just a different way

of doing async. !

It’s still uncomfortable. It’s still circles!

Page 31: How to actually use promises - Jakob Mattsson, FishBrain

The point of promises:

Page 32: How to actually use promises - Jakob Mattsson, FishBrain

!

Make async code as easy to write

as sync code

The point of promises:

Page 33: How to actually use promises - Jakob Mattsson, FishBrain

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

Page 34: How to actually use promises - Jakob Mattsson, FishBrain

Let me elaborate

Page 35: How to actually use promises - Jakob Mattsson, FishBrain

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

Page 36: How to actually use promises - Jakob Mattsson, FishBrain

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.

Page 37: How to actually use promises - Jakob Mattsson, FishBrain

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.

Page 38: How to actually use promises - Jakob Mattsson, FishBrain

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 }); }); }); }); });

Page 39: How to actually use promises - Jakob Mattsson, FishBrain

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.

Page 40: How to actually use promises - Jakob Mattsson, FishBrain

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.

Page 41: How to actually use promises - Jakob Mattsson, FishBrain
Page 42: How to actually use promises - Jakob Mattsson, FishBrain

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

Page 43: How to actually use promises - Jakob Mattsson, FishBrain

Augmenting objects is a sensitive topic

Page 44: How to actually use promises - Jakob Mattsson, FishBrain

Extending prototypes !

vs !

wrapping objects

Page 45: How to actually use promises - Jakob Mattsson, FishBrain

You’re writing Course of action

An app Do whatever you want

A library Do not fucking modify thegod damn prototypes

Complimentary decision matrix

Page 46: How to actually use promises - Jakob Mattsson, FishBrain
Page 47: How to actually use promises - Jakob Mattsson, FishBrain

Promises are already wrapped objects

Page 48: How to actually use promises - Jakob Mattsson, FishBrain

_(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)

Page 49: How to actually use promises - Jakob Mattsson, FishBrain

Promises already have a well-defined way of

unwrapping.

Page 50: How to actually use promises - Jakob Mattsson, FishBrain

_(names) .unique() .shuffle() .first(3) .then(function(values) { // do stuff with values... })

If underscore/lodash wrapped promises

Page 51: How to actually use promises - Jakob Mattsson, FishBrain

But they don’t

Page 52: How to actually use promises - Jakob Mattsson, FishBrain

Enter !

Z

Page 53: How to actually use promises - Jakob Mattsson, FishBrain

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?

Page 54: How to actually use promises - Jakob Mattsson, FishBrain

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

Page 55: How to actually use promises - Jakob Mattsson, FishBrain

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

Page 56: How to actually use promises - Jakob Mattsson, FishBrain

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

Page 57: How to actually use promises - Jakob Mattsson, FishBrain

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”

Page 58: How to actually use promises - Jakob Mattsson, FishBrain
Page 59: How to actually use promises - Jakob Mattsson, FishBrain

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

Page 60: How to actually use promises - Jakob Mattsson, FishBrain

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

Page 61: How to actually use promises - Jakob Mattsson, FishBrain

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

Page 62: How to actually use promises - Jakob Mattsson, FishBrain

Make async code as easy to write

as sync code

Enough with the madness

Page 63: How to actually use promises - Jakob Mattsson, FishBrain

You don’t have to use Z to do these things

Page 64: How to actually use promises - Jakob Mattsson, FishBrain

But FFS

Page 65: How to actually use promises - Jakob Mattsson, FishBrain
Page 66: How to actually use promises - Jakob Mattsson, FishBrain

#noandthenwww.jakobm.com @jakobmattsson

!

github.com/jakobmattsson/z-core

bit.ly/sthlmjs+-

Enough with the madness