Promises are so passé - Tim Perry - Codemotion Milan 2016
-
Upload
codemotion -
Category
Technology
-
view
131 -
download
0
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