Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Promises Are So Passé

8a1aabc40d859fcb786eb4d28b95d299?s=47 Tim Perry
September 02, 2016

Promises Are So Passé

8a1aabc40d859fcb786eb4d28b95d299?s=128

Tim Perry

September 02, 2016
Tweet

Transcript

  1. Promises ARE SO PASSÉ Tim Perry - @pimterry - Senior

    software engineer at resin.io
  2. None
  3. "Any fool can write code that a computer can understand.

    Good programmers write code that humans can understand." — Martin Fowler
  4. LET'S TALK ABOUT JavaScript

  5. Async

  6. JavaScript REMOVES ASYNC CHALLENGES BUT CAN'T DIRECTLY EXPRESS ASYNC

  7. JavaScript REMOVES ASYNC CHALLENGES

  8. EVENT-DRIVEN RUN-TO-COMPLETION SINGLE-THREADING

  9. WE HAVE AN ASYNCHRONOUS ECOSYSTEM

  10. SIMPLER BUT NOT SIMPLE

  11. JavaScript CAN'T DIRECTLY EXPRESS ASYNC

  12. WE NEED HIGH-LEVEL ASYNC CONSTRUCTS

  13. Callbacks

  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) } }
  17. Promises

  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

  34. Generators

  35. GENERATORS ARE FUNCTIONS THAT REPEATEDLY YIELD VALUES

  36. GENERATORS ARE FUNCTIONS THAT PAUSE

  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 }
  41. Promises + Generators

  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

  47. Async / Await

  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; }
  52. THIS IS NOT ALL ROSES

  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
  62. None
  63. THESE ARE OUR HIGH-LEVEL ASYNC CONSTRUCTS

  64. Promises ARE SO PASSÉ Tim Perry - @pimterry - Senior

    software engineer at resin.io