Promise to test it - jsday Verona

Promise to test it - jsday Verona

7dd731d0c97e334d726f740a710904a9?s=128

Jakob Mattsson

May 14, 2014
Tweet

Transcript

  1. The lack and/or misuse of promises Promise to test it

    jakobm.com @jakobmattsson
  2. None
  3. None
  4. Promises 101* *No, this is not a tutorial

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

    } });
  6. 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' });
  7. 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!
  8. Why is that a good idea? • Loosen up the

    coupling • Superior error handling • Simplified async
  9. That’s enough 101

  10. Promises are not new http://github.com/kriskowal/q http://www.html5rocks.com/en/tutorials/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
  11. They’ve even made it into ES6

  12. They’ve even made it into ES6 Already implemented natively in

    Firefox 30 Chrome 33
  13. So why are you not using them?

  14. So why are you not using them?

  15. How to draw an owl

  16. 1. Draw some circles How to draw an owl

  17. 1. Draw some circles 2. Draw the rest of the

    fucking owl How to draw an owl
  18. We want to draw owls. ! Not circles.

  19. 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
  20. That’s circles. ! Nice circles! ! Still circles.

  21. 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
  22. 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
  23. Used to be callback-hell. ! Now it is then-hell.

  24. None
  25. None
  26. These examples are just a different way of doing async.

    ! It’s still uncomfortable. It’s still circles!
  27. The point of promises:

  28. ! Make async code as easy to write as sync

    code The point of promises:
  29. 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
  30. Let me elaborate

  31. 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
  32. 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.
  33. 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.
  34. 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 }); }); }); }); });
  35. 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.
  36. 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.
  37. None
  38. 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
  39. Augmenting objects is a sensitive topic

  40. Extending prototypes ! vs ! wrapping objects

  41. You’re writing Course of action An app Do whatever you

    want A library Do not modify the
 god damn prototypes Complimentary decision matrix
  42. None
  43. Promises are already wrapped objects

  44. _(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)
  45. Promises already have a well-defined way of unwrapping.

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

    values... }) If underscore/lodash wrapped promises
  47. But they don’t

  48. Enter ! Z

  49. 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?
  50. 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
  51. 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
  52. 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
  53. 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”
  54. None
  55. 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
  56. 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
  57. 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
  58. Make async code as easy to write as sync code

    Enough with the madness
  59. You don’t have to use a lib to do these

    things
  60. But FFS

  61. None
  62. #noandthen www.jakobm.com @jakobmattsson ! github.com/jakobmattsson/z-core Enough with the madness