Slide 1

Slide 1 text

How a Promise can get you out of callback hell Andrea La)uada twi$er.com/utaal, github.com/utaal buildo

Slide 2

Slide 2 text

function postMeetupComment(userId, meetupId, entry, callback) { db.getMeetupById(meetupId, function(err, meetup) { if (err) callback(err); if (!meetup.canceled) { db.getAttendees(meetupId, function(err, attendees) { if (err) callback(err); if (attendees.contains(userId)) { swearWordService.check(entry, function(err, clean) { if (err) callback(err); if (clean) { db.postComment(userId, meetupId, entry, callback); } else { callback("The post contains inappropriate words"); } }); } else { callback("User is not attending the meetup"); } } } else { callback("The meetup was canceled"); } }); }

Slide 3

Slide 3 text

function postMeetupComment(userId, meetupId, entry, _) { var meetup = db.getMeetupById(meetupId, _); if (meetup.canceled) { throw "The meetup was canceled."; } var attendees = db.getAttendees(meetupId, _); if (!attendees.contains(userId)) { throw "User is not attending the meetup"; } var clean = swearWordService.check(entry, _); if (!clean) { throw "The post contains inappropriate words"; } cb.postComment(userId, meetupId, entry, _); }

Slide 4

Slide 4 text

$(document).ready(function () { $.get('data/meetup/milanojs.json', function(milanojs) { var events = []; milanojs.events.map(function (eId) { $.get('data/meetup/events/' + eId + '.json', function(evnt) { events.push(evnt); if (events.length == milanojs.events.length) { // we got all resopnses Object.assign(milanojs, { events: events }); $('.output').html(template(milanojs)); } }); }); }); });

Slide 5

Slide 5 text

await promisify($(document).ready)(); var milanojs = await promisify($.get)('data/meetup/milanojs.json'); var events = milanojs.events.map(function (eId) { return await promisify($.get)('data/meetup/events/' + eId + '.json'); }); milanojs = Object.assign(milanojs, { events: events }); $('.output').html(template(milanojs));

Slide 6

Slide 6 text

Javascript's programming model

Slide 7

Slide 7 text

No content

Slide 8

Slide 8 text

No content

Slide 9

Slide 9 text

No content

Slide 10

Slide 10 text

No content

Slide 11

Slide 11 text

Con$nua$on passing style

Slide 12

Slide 12 text

$(document).ready(function () { $.get('data/meetup/milanojs.json', function(milanojs) { var latestEventId = milanojs.events[0]; $.get('data/metup/events/' + latestEventId + '.json', cont(evnt) { $('.output').html(template(evnt)); }); }); });

Slide 13

Slide 13 text

function postMeetupComment(userId, meetupId, entry, callback) { db.getMeetupById(meetupId, function(err, meetup) { if (err) callback(err); if (!meetup.canceled) { db.getAttendees(meetupId, function(err, attendees) { if (err) callback(err); if (attendees.contains(userId)) { swearWordService.check(entry, function(err, clean) { if (err) callback(err); if (clean) { db.postComment(userId, meetupId, entry, callback); } else { callback("The post contains inappropriate words"); } }); } else { callback("User is not attending the meetup"); } } } else { callback("The meetup was canceled"); } }); }

Slide 14

Slide 14 text

Born on the server

Slide 15

Slide 15 text

Con$nua$on-passing-style transforma)on

Slide 16

Slide 16 text

con$nua$on.js

Slide 17

Slide 17 text

$(document).ready(function () { $.get('data/meetup/milanojs.json', function(milanojs) { var latestEventId = milanojs.events[0]; $.get('data/metup/events/' + latestEventId + '.json', function(evnt) { $('.output').html(template(evnt)); }); }); }); ⁵ $(document).ready(cont()); $.get('data/meetup/milanojs.json', cont(milanojs)); var latestEventId = milanojs.events[0]; $.get('data/meetup/events/' + eId + '.json', cont(evnt)); $('.output').html(template(evnt));

Slide 18

Slide 18 text

function postMeetupComment(userId, meetupId, entry, callback) { db.getMeetupById(meetupId, function(err, meetup) { if (err) callback(err); if (meetup.canceled) { callback("The meetup was canceled"); } else { db.getAttendees(meetupId, function(err, attendees) { if (err) callback(err); if (!attendees.contains(userId)) { callback("User is not attending the meetup"); } else { swearWordService.check(entry, function(err, clean) { if (err) callback(err); if (!clean) { callback("The post contains inappropriate words"); } else { db.postComment(userId, meetupId, entry, callback); } }); } } } }); }

Slide 19

Slide 19 text

streamline.js

Slide 20

Slide 20 text

function postMeetupComment(userId, meetupId, entry, _) { var meetup = db.getMeetupById(meetupId, _); if (meetup.canceled) { throw "The meetup was canceled."; } var attendees = db.getAttendees(meetupId, _); if (!attendees.contains(userId)) { throw "User is not attending the meetup"; } var clean = swearWordService.check(entry, _); if (!clean) { throw "The post contains inappropriate words"; } cb.postComment(userId, meetupId, entry, _); }

Slide 21

Slide 21 text

Con$nua$on-passing-style transform con$nua$on.js, streamline.js • requires transpiling (build step) • requires run3me (streamline.js) • intui3ve syntax • non-standard

Slide 22

Slide 22 text

Can we do something with just a Library?

Slide 23

Slide 23 text

function postMeetupComment(userId, meetupId, entry, callback) { db.getMeetupById(meetupId, function(err, meetup) { if (err) callback(err); if (!meetup.canceled) { db.getAttendees(meetupId, function(err, attendees) { if (err) callback(err); if (attendees.contains(userId)) { swearWordService.check(entry, function(err, clean) { if (err) callback(err); if (clean) { db.postComment(userId, meetupId, entry, callback); } else { callback("The post contains inappropriate words"); } }); } else { callback("User is not attending the meetup"); } } } else { callback("The meetup was canceled"); } }); }

Slide 24

Slide 24 text

async.js

Slide 25

Slide 25 text

function postMeetupComment(userId, meetupId, entry, callback) { async.waterfall([ function(callback) { db.getMeetupById(meetupId, callback); }, function(meetup, callback) { if (meetup.canceled) { callback("The meetup was candeled."); } else { db.getAttendees(meetupId, callback); } }, function(attendees, callback) { if (!attendees.contains(userId)) { callback("User is not attending the meetup"); } else { swearWordService.check(entry, callback); } }, function(clean, callback) { if (!clean) { callback("The post contains inappropriate words"); } else { db.postComment(userId, meetupId, entry, callback); } } ], callback); }

Slide 26

Slide 26 text

Library-only async.js • simple • minimal dependency • no build step • boilerplaty

Slide 27

Slide 27 text

You promised me a Promise

Slide 28

Slide 28 text

No content

Slide 29

Slide 29 text

No content

Slide 30

Slide 30 text

No content

Slide 31

Slide 31 text

No content

Slide 32

Slide 32 text

No content

Slide 33

Slide 33 text

function timeoutPromise(delay) { return new Promise(function(resolve, reject) { setTimeout(function() { resolve(true) }, delay); }); } var p = timeoutPromise(20000); p.then(function() { console.log('hi!'); // printed 20 seconds after timeoutPromise was invoked });

Slide 34

Slide 34 text

$(document).ready(function () { $.get('data/meetup/milanojs.json', function(milanojs) { var latestEventId = milanojs.events[0]; $.get('data/metup/events/' + latestEventId + '.json', function(evnt) { $('.output').html(template(evnt)); }); }); }); → promisify($(document).ready)().then(function() { return promisify($.get)('data/meetup/milanojs.json'); }).then(function(milanojs) { var latestEventId = milanojs.events[0]; return promisify($.get)('data/meetup/events/' + eId + '.json'); }).then(function(evnt) { $('.output').html(template(evnt)); });

Slide 35

Slide 35 text

$(document).ready(function () { $.get('data/meetup/milanojs.json', function(milanojs) { var events = []; milanojs.events.map(function (eId) { $.get('data/meetup/events/' + eId + '.json', function(evnt) { events.push(evnt); if (events.length == milanojs.events.length) { // we got all resopnses milanojs = Object.assign(milanojs, { events: events }); $('.output').html(template(milanojs)); } }); }); }); }); → promisify($(document).ready)().then(function() { return promisify($.get)('data/meetup/milanojs.json'); }).then(function(milanojs) { return Promise.all(milanojs.events.map(function (eId) { return promisify($.get)('data/meetup/events/' + eId + '.json'); })).then(function(events) { return Object.assign(milanojs, { events: events }); }); }).then(function(data) { $('.output').html(template(data)); });

Slide 36

Slide 36 text

$.getPromise = function(url) { return new Promise(function(resolve, reject) { $.get(url, function(result) { resolve(result); }); }); } → $.getPromise = function(url) { return new Promise(function(resolve, reject) { $.get(url, resolve); }); }

Slide 37

Slide 37 text

function postMeetupComment(userId, meetupId, entry, callback) { db.getMeetupById(meetupId, function(err, meetup) { if (err) callback(err); if (!meetup.canceled) { db.getAttendees(meetupId, function(err, attendees) { if (err) callback(err); if (attendees.contains(userId)) { swearWordService.check(entry, function(err, clean) { if (err) callback(err); if (clean) { db.postComment(userId, meetupId, entry, callback); } else { callback("The post contains inappropriate words"); } }); } else { callback("User is not attending the meetup"); } } } else { callback("The meetup was canceled"); } }); }

Slide 38

Slide 38 text

var db = promisifyAll(db); function postMeetupComment(userId, meetupId, entry) { return db.getMeetupById(meetupId).then(function(meetup) { if (meetup.canceled) { throw "The meetup was candeled."; } else { return db.getAttendees(meetupId); } }).then(function(attendees) { if (!attendees.contains(userId)) { throw "User is not attending the meetup"; } else { return swearWordService.check(entry); } }).then(function(clean) { if (!clean) { throw "The post contains inappropriate words"; } else { return db.postComment(userId, meetupId, entry); } }); }

Slide 39

Slide 39 text

db = promisifyAll(db); function postMeetupComment(userId, meetupId, entry) { return db.getMeetupById(meetupId).then(function(meetup) { if (meetup.canceled) { throw "The meetup was candeled."; } else { return db.getAttendees(meetupId).then(function(attendees) { if (!attendees.contains(userId)) { throw "User is not attending the meetup"; } else { return swearWordService.check(entry); } }).then(function(clean) { if (meetup.familySafe && !clean) { throw "The post contains inappropriate words"; } else { return db.postComment(userId, meetupId, entry); } }); } }); }

Slide 40

Slide 40 text

How a Promise can get you out of callback hell and into Promise hell

Slide 41

Slide 41 text

Promise ES6, polyfill • needs polyfill for IE and older browsers • no build step (no transforma8on) • standard (hopefully not going away) • syntax is some8mes cumbersome • need to wrap callback-based apis

Slide 42

Slide 42 text

Enter the co-rou&ne

Slide 43

Slide 43 text

No content

Slide 44

Slide 44 text

async and await from the future (ES7)

Slide 45

Slide 45 text

$(document).ready(function () { $.get('data/meetup/milanojs.json', function(milanojs) { var events = []; milanojs.events.map(function (eId) { $.get('data/meetup/events/' + eId + '.json', function(evnt) { events.push(evnt); if (events.length == milanojs.events.length) { // we got all resopnses Object.assign(milanojs, { events: events }); $('.output').html(template(milanojs)); } }); }); }); }); → async function main() { await promisify($(document).ready)(); var milanojs = await promisify($.get)('data/meetup/milanojs.json'); var events = milanojs.events.map(function (eId) { return await $.get('data/meetup/events/' + eId + '.json'); }; milanojs = Object.assign(milanojs, { events: events }); $('.output').html(template(milanojs)); }); main();

Slide 46

Slide 46 text

function main() { var milanojs, events, data; return _regeneratorRuntime.async(function main$(context$1$0) { while (1) switch (context$1$0.prev = context$1$0.next) { case 0: context$1$0.next = 2; return _regeneratorRuntime.awrap(promisify($(document).ready)); case 2: context$1$0.next = 4; return _regeneratorRuntime.awrap(promisify($.get)('data/meetup/milanojs.json')); case 4: milanojs = context$1$0.sent; console.log(milanojs); context$1$0.next = 8; return _regeneratorRuntime.awrap(_Promise.all(milanojs.events.map(function (ev) { return promisify($.get)('data/meetup/events/' + ev + '.json'); }))); case 8: events = context$1$0.sent; milanojs = _Object$assign(milanojs, { events: events }); console.log(data); $('.output').html(prettyHtml(milanojs)); case 12: case 'end': return context$1$0.stop(); } }, null, this); }

Slide 47

Slide 47 text

function main() { var milanojs, events, data; return _regeneratorRuntime.async(function main$(context$1$0) { while (1) switch (context$1$0.prev = context$1$0.next) { case 0: context$1$0.next = 2; return _regeneratorRuntime.awrap(promisify($(document).ready)); case 2: context$1$0.next = 4; return _regeneratorRuntime.awrap(promisify($.get)('data/meetup/milanojs.json')); case 4: milanojs = context$1$0.sent; console.log(milanojs); context$1$0.next = 8; return _regeneratorRuntime.awrap(_Promise.all(milanojs.events.map(function (ev) { return promisify($.get)('data/meetup/events/' + ev + '.json'); }))); case 8: events = context$1$0.sent; milanojs = _Object$assign(milanojs, { events: events }); console.log(data); $('.output').html(prettyHtml(milanojs)); case 12: case 'end': return context$1$0.stop(); } }, null, this); }

Slide 48

Slide 48 text

db = promisifyAll(db); function postMeetupComment(userId, meetupId, entry) { return db.getMeetupById(meetupId).then(function(meetup) { if (meetup.canceled) { throw "The meetup was candeled."; } else { return db.getAttendees(meetupId).then(function(attendees) { if (!attendees.contains(userId)) { throw "User is not attending the meetup"; } else { return swearWordService.check(entry); } }).then(function(clean) { if (meetup.familySafe && !clean) { throw "The post contains inappropriate words"; } else { return db.postComment(userId, meetupId, entry); } }); } }); }

Slide 49

Slide 49 text

db = promisifyAll(db); async function postMeetupComment(userId, meetupId, entry) { var meetup = await db.getMeetupById(meetupId)); if (meetup.canceled) { throw "The meetup was candeled."; } var attendees = await db.getAttendees(meetupId); if (!attendees.contains(userId)) { throw "User is not attending the meetup"; } var clean = await swearWordService.check(entry); if (meetup.familySafe && !clean) { throw "The post contains inappropriate words"; } await db.postComment(userId, meetupId, entry); }

Slide 50

Slide 50 text

Async / await ES7, babel + run.me • need build step (transpiler) • need run1me • clean, intui1ve syntax • requires promise-based api (promisify)

Slide 51

Slide 51 text

db = promisifyAll(db); async function postMeetupComment(userId, meetupId, entry) { var meetup = await db.getMeetupById(meetupId)); if (meetup.canceled) { throw "The meetup was candeled."; } var attendees = await db.getAttendees(meetupId); if (!attendees.contains(userId)) { throw "User is not attending the meetup"; } var clean = await swearWordService.check(entry); if (meetup.familySafe && !clean) { throw "The post contains inappropriate words"; } await db.postComment(userId, meetupId, entry); }

Slide 52

Slide 52 text

And what about generators? You know, those with the * and yield

Slide 53

Slide 53 text

Use source maps for debuggability, ES7 cannot come too soon

Slide 54

Slide 54 text

db = promisifyAll(db); function postMeetupComment(userId, meetupId, entry) { Fiber(function() { var meetup = db.getMeetupById(meetupId); if (meetup.canceled) { throw "The meetup was candeled."; } var attendees = db.getAttendees(meetupId); if (!attendees.contains(userId)) { throw "User is not attending the meetup"; } var clean = swearWordService.check(entry); if (meetup.familySafe && !clean) { throw "The post contains inappropriate words"; } db.postComment(userId, meetupId, entry); }).run(); }

Slide 55

Slide 55 text

Try them out, choose what works best in your environment

Slide 56

Slide 56 text

No content

Slide 57

Slide 57 text

Thanks!