Slide 1

Slide 1 text

Promises ARE SO PASSÉ Tim Perry - @pimterry - Senior software engineer at resin.io

Slide 2

Slide 2 text

No content

Slide 3

Slide 3 text

"Any fool can write code that a computer can understand. Good programmers write code that humans can understand." — Martin Fowler

Slide 4

Slide 4 text

LET'S TALK ABOUT JavaScript

Slide 5

Slide 5 text

Async

Slide 6

Slide 6 text

JavaScript REMOVES ASYNC CHALLENGES BUT CAN'T DIRECTLY EXPRESS ASYNC

Slide 7

Slide 7 text

JavaScript REMOVES ASYNC CHALLENGES

Slide 8

Slide 8 text

EVENT-DRIVEN RUN-TO-COMPLETION SINGLE-THREADING

Slide 9

Slide 9 text

WE HAVE AN ASYNCHRONOUS ECOSYSTEM

Slide 10

Slide 10 text

SIMPLER BUT NOT SIMPLE

Slide 11

Slide 11 text

JavaScript CAN'T DIRECTLY EXPRESS ASYNC

Slide 12

Slide 12 text

WE NEED HIGH-LEVEL ASYNC CONSTRUCTS

Slide 13

Slide 13 text

Callbacks

Slide 14

Slide 14 text

PATTERN: SEQUENTIAL DEPENDENT OPERATIONS - Get the user from the database - Get that user's best friend - Get that best friend's profile picture - Add a mustache to everybody in the picture - Show the transformed picture on the page

Slide 15

Slide 15 text

PATTERN: SEQUENTIAL DEPENDENT OPERATIONS function mustachify(userId, callback) { loadUserFromDatabase(userId, function (err, user) { if (err) callback(err); else user.getBestFriend(function (err, friend) { if (err) callback(err); else friend.getBestPhoto(function (err, photo) { if (err) callback(err); else addMustache(photo, function (err, betterPhoto) { if (err) callback(err); else showPhotoToUser(user, betterPhoto, callback); }); }); }); }); }

Slide 16

Slide 16 text

PATTERN: CROSS-OPERATION ERROR HANDLING function mustachify(userId, callback) { try { loadUserFromDatabase(userId, function (err, user) { if (err) callback(err); else { try { user.getBestFriend(function (err, friend) { if (err) callback(err); else { try { friend.getBestPhoto(function (err, photo) { if (err) callback(err); else { try { addMustache(photo, function (err, betterPhoto) { if (err) callback(err); else { try { showPhotoToUser(user, betterPhoto, callback); } catch (e) { callback(e) } } }); } catch (e) { callback(e) } } }); } catch (e) { callback(e) } } }); } catch (e) { callback(e) } } }); } catch (e) { callback(e) } }

Slide 17

Slide 17 text

Promises

Slide 18

Slide 18 text

PROMISES RECAP A promise is a representation of a (potentially) ongoing process. ▸ Pending ▸ Fulfilled ▸ Rejected

Slide 19

Slide 19 text

PROMISES RECAP Define a new promise: var p = new Promise(function (resolve, reject) { // ... Do some asynchronous things // Eventually call resolve or reject }); Promise.resolve({ myResult: 123 }); Promise.reject(new Error("BAD THINGS"));

Slide 20

Slide 20 text

PROMISES RECAP Chain promises together: loadData().then(function (data) { return transformData(data); }).then(function (transformedData) { showData(data); });

Slide 21

Slide 21 text

PROMISES RECAP Catch errors across steps: loadData().then(function (data) { return transformData(data); }).then(function (transformedData) { showData(data); }).catch(function (error) { console.log(error); });

Slide 22

Slide 22 text

PROMISES RECAP Combine promises: var allData = Promise.all([ loadData("company-one"), loadData("company-two") ]); var firstResponse = Promise.race([ getDataFromDataCenter1(), getDataFromDataCenter2() ]);

Slide 23

Slide 23 text

PROMISES ARE OUR CURRENT BEST SOLUTION

Slide 24

Slide 24 text

PROMISES STILL HAVE TOO MUCH CEREMONY

Slide 25

Slide 25 text

PROMISES STILL COUPLE FUNCTION SCOPES WITH ASYNC OPERATIONS

Slide 26

Slide 26 text

PATTERN: COMBINING SEQUENTIAL RESULTS - Get the current user's data - Load the timeline for that user - Show the user's details & timeline on screen

Slide 27

Slide 27 text

PATTERN: COMBINING SEQUENTIAL RESULTS var user; getUser().then(function (userData) { user = userData; // <- Ick return getTimeline(user); }).then(function (timeline) { showUser(user); showTimeline(timeline); });

Slide 28

Slide 28 text

PATTERN: ASYNC IN A LOOP - For each task: - Run the task to completion - Add the result to the task results array - Move on to the next task - Return the array of task results

Slide 29

Slide 29 text

PATTERN: ASYNC IN A LOOP function runTasks(tasks) { var accumulatedPromise = Promise.resolve([]); for (var task of tasks) { accumulatedPromise.then(function (accumulatedResults) { return task.run().then(function (result) { return accumulatedResults.concat(result); }); }); } return accumulatedPromise; }

Slide 30

Slide 30 text

PATTERN: CONDITIONAL ON ASYNC RESULT - If the server is up and the user's login is valid: - Save their data - Show a nice message - Otherwise: - Show an error

Slide 31

Slide 31 text

PATTERN: CONDITIONAL ON ASYNC RESULT isServerAvailable().then(function (serverIsAvailable) { return serverIsAvailable && isAuthenticationValid(); }).then(function (canSave) { if (canSave) { return saveData().then(function () { showMessage("Data saved"); }); } else { showWarning("Couldn't save data"); } });

Slide 32

Slide 32 text

PROMISES GIVE US A MODEL FOR ASYNC PROCESSES

Slide 33

Slide 33 text

BUT THEIR API DOESN'T PLAY NICELY WITH EXISTING CONSTRUCTS

Slide 34

Slide 34 text

Generators

Slide 35

Slide 35 text

GENERATORS ARE FUNCTIONS THAT REPEATEDLY YIELD VALUES

Slide 36

Slide 36 text

GENERATORS ARE FUNCTIONS THAT PAUSE

Slide 37

Slide 37 text

function* generateEveryNumber() { var n = 0; while(true) { yield n; n += 1; } }

Slide 38

Slide 38 text

function* generateEveryNumber() { var n = 0; while(true) { yield n; n += 1; } } var allNumbers = generateEveryNumber(); allNumbers.next(); // { value: 0, done: false } allNumbers.next(); // { value: 1, done: false } allNumbers.next(); // { value: 2, done: false }

Slide 39

Slide 39 text

function* generateEveryNumber() { var n = 0; while(true) { yield n; n += 1; } } for (var number of generateEveryNumber()) { if (number > 1000) break; console.log(number); // 1, 2, 3 ... 1000 }

Slide 40

Slide 40 text

function* sumUpNumbers() { var accumulator = (yield); while (true) { var nextAddition = (yield accumulator); accumulator += nextAddition; } } var sum = sumUpNumbers(); sum.next(); // Need an initial next(), to run to first yield. sum.next(1); // { value: 1, done: false } sum.next(5); // { value: 6, done: false }

Slide 41

Slide 41 text

Promises + Generators

Slide 42

Slide 42 text

PATTERN: CONDITIONAL ON ASYNC RESULT - If the server is up and the user's login is valid: - Save their data - Show a nice message - Otherwise: - Show an error

Slide 43

Slide 43 text

PATTERN: CONDITIONAL ON ASYNC RESULT spawn(function* () { if ((yield isServerAccessible()) && (yield isAuthenticationValid())) { yield saveData(); showMessage("Data saved"); } else { showWarning("Couldn't save data"); } });

Slide 44

Slide 44 text

WRAPPING A GENERATOR function spawn(generatorConstructor) { var generator = generatorConstructor(); function step(input) { var result = generator.next(input); var value = Promise.resolve(result.value); if (result.done) return value; else return value.then(step); } return step(); } // (Error handling omitted)

Slide 45

Slide 45 text

PATTERN: CONDITIONAL ON ASYNC RESULT spawn(function* () { if ((yield isServerAccessible()) && (yield isAuthenticationValid())) { yield saveData(); showMessage("Data saved"); } else { showWarning("Couldn't save data"); } });

Slide 46

Slide 46 text

PROMISES ARE DEAD LONG LIVE PROMISES

Slide 47

Slide 47 text

Async / Await

Slide 48

Slide 48 text

ASYNC/AWAIT IS THE SAME MAGIC BUT BUILT IN

Slide 49

Slide 49 text

PATTERN: CONDITIONAL ON ASYNC RESULT async function save() { if ((await isServerAccessible()) && (await isAuthenticationValid())) { await saveData(); showMessage("Data saved"); } else { showWarning("Couldn't save data"); } }

Slide 50

Slide 50 text

PATTERN: COMBINING SEQUENTIAL RESULTS async function showUserAndTimeline() { var user = await getUser(); var timeline = await getTimeline(user); showUser(user); showTimeline(timeline); }

Slide 51

Slide 51 text

PATTERN: ASYNC IN A LOOP async function runTasks(tasks) { var results = []; for (task of tasks) { results.push(await task.run()); } return results; }

Slide 52

Slide 52 text

THIS IS NOT ALL ROSES

Slide 53

Slide 53 text

GOTCHA: YOU STILL HAVE TO CATCH ERRORS

Slide 54

Slide 54 text

ASYNC/AWAIT ERROR HANDLING async function mustachify(userId) { var user = await loadUserFromDatabase(userId); var friend = await getBestFriend(user); var photo = await friend.getBestPhoto(); var betterPhoto = await addMustache(photo); return showPhotoToUser(user, betterPhoto); } mustachify(userId).catch(function (error) { // You can still use catch() - it's promises under the hood });

Slide 55

Slide 55 text

ASYNC/AWAIT ERROR HANDLING async function mustachify(userId) { try { var user = await loadUserFromDatabase(userId); var friend = await getBestFriend(user); var photo = await friend.getBestPhoto(); var betterPhoto = await addMustache(photo); return showPhotoToUser(user, betterPhoto); } catch (error) { // Try/catch now works with promises & async too } } mustachify(userId);

Slide 56

Slide 56 text

GOTCHA: CAN'T AWAIT IN NESTED FUNCTIONS async function getAllFriendNames() { var friendData = friendIds.map(function (friendId) { // This code isn't in an async function, // so this is a syntax error. return (await getUserData(friendId)); }); return friendData.map(function (friend) { return friend.name; }); }

Slide 57

Slide 57 text

GOTCHA: CAN'T AWAIT IN NESTED FUNCTIONS async function getAllFriendNames() { var friendData = friendIds.map(async function (friendId) { return (await getUserData(friendId)); }); // Map() doesn't care about async, so our getUserData // calls haven't actually finished yet. return friendData.map(function (friend) { return friend.name; }); }

Slide 58

Slide 58 text

GOTCHA: CAN'T AWAIT IN NESTED FUNCTIONS async function getAllFriendNames() { var friendData = await Promise.all( friendIds.map(function (friendId) { return getUserData(friendId); }) ); return friendData.map(function (friend) { return friend.name; }); }

Slide 59

Slide 59 text

GOTCHA: EASY TO OVERUSE AWAIT

Slide 60

Slide 60 text

HOW DO YOU START USING THIS?

Slide 61

Slide 61 text

Available in Chrome Behind a flag in Edge Coming in FireFox 52 Implemented in WebKit

Slide 62

Slide 62 text

No content

Slide 63

Slide 63 text

THESE ARE OUR HIGH-LEVEL ASYNC CONSTRUCTS

Slide 64

Slide 64 text

Promises ARE SO PASSÉ Tim Perry - @pimterry - Senior software engineer at resin.io