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

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

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

Page 1: Promises are so passé - Tim Perry - Codemotion Milan 2016

Promises ARESO PASSÉ

Tim Perry - @pimterry - Senior software engineer at resin.io

Page 2: Promises are so passé - Tim Perry - Codemotion Milan 2016
Page 3: 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

Page 4: Promises are so passé - Tim Perry - Codemotion Milan 2016

LET'S TALK ABOUT

JavaScript

Page 5: Promises are so passé - Tim Perry - Codemotion Milan 2016

Async

Page 6: Promises are so passé - Tim Perry - Codemotion Milan 2016

JavaScriptREMOVES ASYNC CHALLENGES

BUTCAN'T DIRECTLY EXPRESS ASYNC

Page 7: Promises are so passé - Tim Perry - Codemotion Milan 2016

JavaScriptREMOVES ASYNC CHALLENGES

Page 8: Promises are so passé - Tim Perry - Codemotion Milan 2016

EVENT-DRIVENRUN-TO-COMPLETIONSINGLE-THREADING

Page 9: Promises are so passé - Tim Perry - Codemotion Milan 2016

WE HAVE ANASYNCHRONOUS

ECOSYSTEM

Page 10: Promises are so passé - Tim Perry - Codemotion Milan 2016

SIMPLERBUT NOT SIMPLE

Page 11: Promises are so passé - Tim Perry - Codemotion Milan 2016

JavaScriptCAN'T DIRECTLY EXPRESS ASYNC

Page 12: Promises are so passé - Tim Perry - Codemotion Milan 2016

WE NEEDHIGH-LEVEL ASYNC CONSTRUCTS

Page 13: Promises are so passé - Tim Perry - Codemotion Milan 2016

Callbacks

Page 14: Promises are so passé - Tim Perry - Codemotion Milan 2016

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

Page 15: Promises are so passé - Tim Perry - Codemotion Milan 2016

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

Page 16: Promises are so passé - Tim Perry - Codemotion Milan 2016

PATTERN: CROSS-OPERATION ERROR HANDLINGfunction 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) }}

Page 17: Promises are so passé - Tim Perry - Codemotion Milan 2016

Promises

Page 18: Promises are so passé - Tim Perry - Codemotion Milan 2016

PROMISES RECAP

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

Page 19: Promises are so passé - Tim Perry - Codemotion Milan 2016

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

Page 20: Promises are so passé - Tim Perry - Codemotion Milan 2016

PROMISES RECAP

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

Page 21: Promises are so passé - Tim Perry - Codemotion Milan 2016

PROMISES RECAP

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

Page 22: Promises are so passé - Tim Perry - Codemotion Milan 2016

PROMISES RECAP

Combine promises:var allData = Promise.all([ loadData("company-one"), loadData("company-two")]);

var firstResponse = Promise.race([ getDataFromDataCenter1(), getDataFromDataCenter2()]);

Page 23: Promises are so passé - Tim Perry - Codemotion Milan 2016

PROMISES ARE OURCURRENT BEST SOLUTION

Page 24: Promises are so passé - Tim Perry - Codemotion Milan 2016

PROMISES STILL HAVETOO MUCH CEREMONY

Page 25: Promises are so passé - Tim Perry - Codemotion Milan 2016

PROMISES STILLCOUPLE FUNCTION SCOPESWITH ASYNC OPERATIONS

Page 26: Promises are so passé - Tim Perry - Codemotion Milan 2016

PATTERN: COMBINING SEQUENTIAL RESULTS

- Get the current user's data- Load the timeline for that user- Show the user's details & timeline on screen

Page 27: Promises are so passé - Tim Perry - Codemotion Milan 2016

PATTERN: COMBINING SEQUENTIAL RESULTS

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

Page 28: Promises are so passé - Tim Perry - Codemotion Milan 2016

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

Page 29: Promises are so passé - Tim Perry - Codemotion Milan 2016

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

Page 30: Promises are so passé - Tim Perry - Codemotion Milan 2016

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

Page 31: Promises are so passé - Tim Perry - Codemotion Milan 2016

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

Page 32: Promises are so passé - Tim Perry - Codemotion Milan 2016

PROMISES GIVE US AMODEL FOR ASYNC

PROCESSES

Page 33: Promises are so passé - Tim Perry - Codemotion Milan 2016

BUT THEIR APIDOESN'T PLAY NICELY

WITH EXISTING CONSTRUCTS

Page 34: Promises are so passé - Tim Perry - Codemotion Milan 2016

Generators

Page 35: Promises are so passé - Tim Perry - Codemotion Milan 2016

GENERATORS ARE FUNCTIONS THATREPEATEDLY YIELD VALUES

Page 36: Promises are so passé - Tim Perry - Codemotion Milan 2016

GENERATORS ARE FUNCTIONS THAT

PAUSE

Page 37: Promises are so passé - Tim Perry - Codemotion Milan 2016

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

Page 38: Promises are so passé - Tim Perry - Codemotion Milan 2016

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 }

Page 39: Promises are so passé - Tim Perry - Codemotion Milan 2016

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}

Page 40: Promises are so passé - Tim Perry - Codemotion Milan 2016

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 }

Page 41: Promises are so passé - Tim Perry - Codemotion Milan 2016

Promises +

Generators

Page 42: Promises are so passé - Tim Perry - Codemotion Milan 2016

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

Page 43: Promises are so passé - Tim Perry - Codemotion Milan 2016

PATTERN: CONDITIONAL ON ASYNC RESULT

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

Page 44: Promises are so passé - Tim Perry - Codemotion Milan 2016

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)

Page 45: Promises are so passé - Tim Perry - Codemotion Milan 2016

PATTERN: CONDITIONAL ON ASYNC RESULT

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

Page 46: Promises are so passé - Tim Perry - Codemotion Milan 2016

PROMISES ARE DEADLONG LIVE PROMISES

Page 47: Promises are so passé - Tim Perry - Codemotion Milan 2016

Async / Await

Page 48: Promises are so passé - Tim Perry - Codemotion Milan 2016

ASYNC/AWAIT ISTHE SAME MAGIC

BUT BUILT IN

Page 49: Promises are so passé - Tim Perry - Codemotion Milan 2016

PATTERN: CONDITIONAL ON ASYNC RESULT

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

Page 50: Promises are so passé - Tim Perry - Codemotion Milan 2016

PATTERN: COMBINING SEQUENTIAL RESULTS

async function showUserAndTimeline() { var user = await getUser(); var timeline = await getTimeline(user);

showUser(user); showTimeline(timeline);}

Page 51: Promises are so passé - Tim Perry - Codemotion Milan 2016

PATTERN: ASYNC IN A LOOP

async function runTasks(tasks) { var results = [];

for (task of tasks) { results.push(await task.run()); }

return results;}

Page 52: Promises are so passé - Tim Perry - Codemotion Milan 2016

THIS ISNOT ALL ROSES

Page 53: Promises are so passé - Tim Perry - Codemotion Milan 2016

GOTCHA: YOU STILL HAVE TO CATCH ERRORS

Page 54: Promises are so passé - Tim Perry - Codemotion Milan 2016

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

Page 55: Promises are so passé - Tim Perry - Codemotion Milan 2016

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

Page 56: Promises are so passé - Tim Perry - Codemotion Milan 2016

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

Page 57: Promises are so passé - Tim Perry - Codemotion Milan 2016

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

Page 58: Promises are so passé - Tim Perry - Codemotion Milan 2016

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

Page 59: Promises are so passé - Tim Perry - Codemotion Milan 2016

GOTCHA: EASY TO OVERUSE AWAIT

Page 60: Promises are so passé - Tim Perry - Codemotion Milan 2016

HOW DO YOUSTART USING THIS?

Page 61: Promises are so passé - Tim Perry - Codemotion Milan 2016

Available in Chrome

Behind a flag in Edge

Coming in FireFox 52

Implemented in WebKit

Page 62: Promises are so passé - Tim Perry - Codemotion Milan 2016
Page 63: Promises are so passé - Tim Perry - Codemotion Milan 2016

THESE ARE OURHIGH-LEVEL ASYNC CONSTRUCTS

Page 64: Promises are so passé - Tim Perry - Codemotion Milan 2016

Promises ARESO PASSÉ

Tim Perry - @pimterry - Senior software engineer at resin.io