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

Post on 16-Apr-2017

132 views 0 download

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

Promises ARESO PASSÉ

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

"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

JavaScriptREMOVES ASYNC CHALLENGES

BUTCAN'T DIRECTLY EXPRESS ASYNC

JavaScriptREMOVES ASYNC CHALLENGES

EVENT-DRIVENRUN-TO-COMPLETIONSINGLE-THREADING

WE HAVE ANASYNCHRONOUS

ECOSYSTEM

SIMPLERBUT NOT SIMPLE

JavaScriptCAN'T DIRECTLY EXPRESS ASYNC

WE NEEDHIGH-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 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) }}

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 OURCURRENT BEST SOLUTION

PROMISES STILL HAVETOO MUCH CEREMONY

PROMISES STILLCOUPLE FUNCTION SCOPESWITH 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 AMODEL FOR ASYNC

PROCESSES

BUT THEIR APIDOESN'T PLAY NICELY

WITH EXISTING CONSTRUCTS

Generators

GENERATORS ARE FUNCTIONS THATREPEATEDLY 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 DEADLONG LIVE PROMISES

Async / Await

ASYNC/AWAIT ISTHE 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 ISNOT 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 YOUSTART USING THIS?

Available in Chrome

Behind a flag in Edge

Coming in FireFox 52

Implemented in WebKit

THESE ARE OURHIGH-LEVEL ASYNC CONSTRUCTS

Promises ARESO PASSÉ

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