SlideShare a Scribd company logo
Promises ARE
SO PASSÉ
Tim Perry - @pimterry - Senior software engineer at resin.io
Promises are so passé - Tim Perry - Codemotion Milan 2016
"Any fool can write code that a computer can understand.
Good programmers write code that humans can understand."
— Martin Fowler
LET'S TALK ABOUT
JavaScript
Async
JavaScript
REMOVES ASYNC CHALLENGES
BUT
CAN'T DIRECTLY EXPRESS ASYNC
JavaScript
REMOVES ASYNC CHALLENGES
EVENT-DRIVEN
RUN-TO-COMPLETION
SINGLE-THREADING
WE HAVE AN
ASYNCHRONOUS
ECOSYSTEM
SIMPLER
BUT NOT SIMPLE
JavaScript
CAN'T DIRECTLY EXPRESS ASYNC
WE NEED
HIGH-LEVEL ASYNC CONSTRUCTS
Callbacks
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
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);
});
});
});
});
}
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) }
}
Promises
PROMISES RECAP
A promise is a representation of a (potentially) ongoing process.
▸ Pending
▸ Fulfilled
▸ Rejected
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"));
PROMISES RECAP
Chain promises together:
loadData().then(function (data) {
return transformData(data);
}).then(function (transformedData) {
showData(data);
});
PROMISES RECAP
Catch errors across steps:
loadData().then(function (data) {
return transformData(data);
}).then(function (transformedData) {
showData(data);
}).catch(function (error) {
console.log(error);
});
PROMISES RECAP
Combine promises:
var allData = Promise.all([
loadData("company-one"),
loadData("company-two")
]);
var firstResponse = Promise.race([
getDataFromDataCenter1(),
getDataFromDataCenter2()
]);
PROMISES ARE OUR
CURRENT BEST SOLUTION
PROMISES STILL HAVE
TOO MUCH CEREMONY
PROMISES STILL
COUPLE FUNCTION SCOPES
WITH ASYNC OPERATIONS
PATTERN: COMBINING SEQUENTIAL RESULTS
- Get the current user's data
- Load the timeline for that user
- Show the user's details & timeline on screen
PATTERN: COMBINING SEQUENTIAL RESULTS
var user;
getUser().then(function (userData) {
user = userData; // <- Ick
return getTimeline(user);
}).then(function (timeline) {
showUser(user);
showTimeline(timeline);
});
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
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;
}
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
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");
}
});
PROMISES GIVE US A
MODEL FOR ASYNC
PROCESSES
BUT THEIR API
DOESN'T PLAY NICELY
WITH EXISTING CONSTRUCTS
Generators
GENERATORS ARE FUNCTIONS THAT
REPEATEDLY YIELD VALUES
GENERATORS ARE FUNCTIONS THAT
PAUSE
function* generateEveryNumber() {
var n = 0;
while(true) {
yield n;
n += 1;
}
}
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 }
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
}
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 }
Promises
+
Generators
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
PATTERN: CONDITIONAL ON ASYNC RESULT
spawn(function* () {
if ((yield isServerAccessible()) &&
(yield isAuthenticationValid())) {
yield saveData();
showMessage("Data saved");
} else {
showWarning("Couldn't save data");
}
});
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)
PATTERN: CONDITIONAL ON ASYNC RESULT
spawn(function* () {
if ((yield isServerAccessible()) &&
(yield isAuthenticationValid())) {
yield saveData();
showMessage("Data saved");
} else {
showWarning("Couldn't save data");
}
});
PROMISES ARE DEAD
LONG LIVE PROMISES
Async / Await
ASYNC/AWAIT IS
THE SAME MAGIC
BUT BUILT IN
PATTERN: CONDITIONAL ON ASYNC RESULT
async function save() {
if ((await isServerAccessible()) &&
(await isAuthenticationValid())) {
await saveData();
showMessage("Data saved");
} else {
showWarning("Couldn't save data");
}
}
PATTERN: COMBINING SEQUENTIAL RESULTS
async function showUserAndTimeline() {
var user = await getUser();
var timeline = await getTimeline(user);
showUser(user);
showTimeline(timeline);
}
PATTERN: ASYNC IN A LOOP
async function runTasks(tasks) {
var results = [];
for (task of tasks) {
results.push(await task.run());
}
return results;
}
THIS IS
NOT ALL ROSES
GOTCHA: YOU STILL HAVE TO CATCH ERRORS
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
});
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);
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;
});
}
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;
});
}
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;
});
}
GOTCHA: EASY TO OVERUSE AWAIT
HOW DO YOU
START USING THIS?
Available in Chrome
Behind a flag in Edge
Coming in FireFox 52
Implemented in WebKit
Promises are so passé - Tim Perry - Codemotion Milan 2016
THESE ARE OUR
HIGH-LEVEL ASYNC CONSTRUCTS
Promises ARE
SO PASSÉ
Tim Perry - @pimterry - Senior software engineer at resin.io

More Related Content

Promises are so passé - Tim Perry - Codemotion Milan 2016

  • 1. Promises ARE SO PASSÉ Tim Perry - @pimterry - Senior software engineer at resin.io
  • 3. "Any fool can write code that a computer can understand. Good programmers write code that humans can understand." — Martin Fowler
  • 14. 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
  • 15. 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); }); }); }); }); }
  • 16. 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) } }
  • 18. PROMISES RECAP A promise is a representation of a (potentially) ongoing process. ▸ Pending ▸ Fulfilled ▸ Rejected
  • 19. 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"));
  • 20. PROMISES RECAP Chain promises together: loadData().then(function (data) { return transformData(data); }).then(function (transformedData) { showData(data); });
  • 21. PROMISES RECAP Catch errors across steps: loadData().then(function (data) { return transformData(data); }).then(function (transformedData) { showData(data); }).catch(function (error) { console.log(error); });
  • 22. PROMISES RECAP Combine promises: var allData = Promise.all([ loadData("company-one"), loadData("company-two") ]); var firstResponse = Promise.race([ getDataFromDataCenter1(), getDataFromDataCenter2() ]);
  • 23. PROMISES ARE OUR CURRENT BEST SOLUTION
  • 24. PROMISES STILL HAVE TOO MUCH CEREMONY
  • 25. PROMISES STILL COUPLE FUNCTION SCOPES WITH ASYNC OPERATIONS
  • 26. PATTERN: COMBINING SEQUENTIAL RESULTS - Get the current user's data - Load the timeline for that user - Show the user's details & timeline on screen
  • 27. PATTERN: COMBINING SEQUENTIAL RESULTS var user; getUser().then(function (userData) { user = userData; // <- Ick return getTimeline(user); }).then(function (timeline) { showUser(user); showTimeline(timeline); });
  • 28. 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
  • 29. 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; }
  • 30. 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
  • 31. 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"); } });
  • 32. PROMISES GIVE US A MODEL FOR ASYNC PROCESSES
  • 33. BUT THEIR API DOESN'T PLAY NICELY WITH EXISTING CONSTRUCTS
  • 35. GENERATORS ARE FUNCTIONS THAT REPEATEDLY YIELD VALUES
  • 37. function* generateEveryNumber() { var n = 0; while(true) { yield n; n += 1; } }
  • 38. 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 }
  • 39. 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 }
  • 40. 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 }
  • 42. 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
  • 43. PATTERN: CONDITIONAL ON ASYNC RESULT spawn(function* () { if ((yield isServerAccessible()) && (yield isAuthenticationValid())) { yield saveData(); showMessage("Data saved"); } else { showWarning("Couldn't save data"); } });
  • 44. 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)
  • 45. PATTERN: CONDITIONAL ON ASYNC RESULT spawn(function* () { if ((yield isServerAccessible()) && (yield isAuthenticationValid())) { yield saveData(); showMessage("Data saved"); } else { showWarning("Couldn't save data"); } });
  • 46. PROMISES ARE DEAD LONG LIVE PROMISES
  • 48. ASYNC/AWAIT IS THE SAME MAGIC BUT BUILT IN
  • 49. PATTERN: CONDITIONAL ON ASYNC RESULT async function save() { if ((await isServerAccessible()) && (await isAuthenticationValid())) { await saveData(); showMessage("Data saved"); } else { showWarning("Couldn't save data"); } }
  • 50. PATTERN: COMBINING SEQUENTIAL RESULTS async function showUserAndTimeline() { var user = await getUser(); var timeline = await getTimeline(user); showUser(user); showTimeline(timeline); }
  • 51. PATTERN: ASYNC IN A LOOP async function runTasks(tasks) { var results = []; for (task of tasks) { results.push(await task.run()); } return results; }
  • 53. GOTCHA: YOU STILL HAVE TO CATCH ERRORS
  • 54. 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 });
  • 55. 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);
  • 56. 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; }); }
  • 57. 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; }); }
  • 58. 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; }); }
  • 59. GOTCHA: EASY TO OVERUSE AWAIT
  • 60. HOW DO YOU START USING THIS?
  • 61. Available in Chrome Behind a flag in Edge Coming in FireFox 52 Implemented in WebKit
  • 63. THESE ARE OUR HIGH-LEVEL ASYNC CONSTRUCTS
  • 64. Promises ARE SO PASSÉ Tim Perry - @pimterry - Senior software engineer at resin.io