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

Promises Are So Passé

Tim Perry
September 02, 2016

Promises Are So Passé

Tim Perry

September 02, 2016
Tweet

More Decks by Tim Perry

Other Decks in Programming

Transcript

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

    View Slide

  2. View Slide

  3. "Any fool can write code that a computer can understand.
    Good programmers write code that humans can understand."
    — Martin Fowler

    View Slide

  4. LET'S TALK ABOUT
    JavaScript

    View Slide

  5. Async

    View Slide

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

    View Slide

  7. JavaScript
    REMOVES ASYNC CHALLENGES

    View Slide

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

    View Slide

  9. WE HAVE AN
    ASYNCHRONOUS
    ECOSYSTEM

    View Slide

  10. SIMPLER
    BUT NOT SIMPLE

    View Slide

  11. JavaScript
    CAN'T DIRECTLY EXPRESS ASYNC

    View Slide

  12. WE NEED
    HIGH-LEVEL ASYNC CONSTRUCTS

    View Slide

  13. Callbacks

    View Slide

  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

    View Slide

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

    View Slide

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

    View Slide

  17. Promises

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  22. PROMISES RECAP
    Combine promises:
    var allData = Promise.all([
    loadData("company-one"),
    loadData("company-two")
    ]);
    var firstResponse = Promise.race([
    getDataFromDataCenter1(),
    getDataFromDataCenter2()
    ]);

    View Slide

  23. PROMISES ARE OUR
    CURRENT BEST SOLUTION

    View Slide

  24. PROMISES STILL HAVE
    TOO MUCH CEREMONY

    View Slide

  25. PROMISES STILL
    COUPLE FUNCTION SCOPES
    WITH ASYNC OPERATIONS

    View Slide

  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

    View Slide

  27. PATTERN: COMBINING SEQUENTIAL RESULTS
    var user;
    getUser().then(function (userData) {
    user = userData; // <- Ick
    return getTimeline(user);
    }).then(function (timeline) {
    showUser(user);
    showTimeline(timeline);
    });

    View Slide

  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

    View Slide

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

    View Slide

  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

    View Slide

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

    View Slide

  32. PROMISES GIVE US A
    MODEL FOR ASYNC
    PROCESSES

    View Slide

  33. BUT THEIR API
    DOESN'T PLAY NICELY
    WITH EXISTING CONSTRUCTS

    View Slide

  34. Generators

    View Slide

  35. GENERATORS ARE FUNCTIONS THAT
    REPEATEDLY YIELD VALUES

    View Slide

  36. GENERATORS ARE FUNCTIONS THAT
    PAUSE

    View Slide

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

    View Slide

  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 }

    View Slide

  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
    }

    View Slide

  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 }

    View Slide

  41. Promises
    +
    Generators

    View Slide

  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

    View Slide

  43. PATTERN: CONDITIONAL ON ASYNC RESULT
    spawn(function* () {
    if ((yield isServerAccessible()) &&
    (yield isAuthenticationValid())) {
    yield saveData();
    showMessage("Data saved");
    } else {
    showWarning("Couldn't save data");
    }
    });

    View Slide

  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)

    View Slide

  45. PATTERN: CONDITIONAL ON ASYNC RESULT
    spawn(function* () {
    if ((yield isServerAccessible()) &&
    (yield isAuthenticationValid())) {
    yield saveData();
    showMessage("Data saved");
    } else {
    showWarning("Couldn't save data");
    }
    });

    View Slide

  46. PROMISES ARE DEAD
    LONG LIVE PROMISES

    View Slide

  47. Async / Await

    View Slide

  48. ASYNC/AWAIT IS
    THE SAME MAGIC
    BUT BUILT IN

    View Slide

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

    View Slide

  50. PATTERN: COMBINING SEQUENTIAL RESULTS
    async function showUserAndTimeline() {
    var user = await getUser();
    var timeline = await getTimeline(user);
    showUser(user);
    showTimeline(timeline);
    }

    View Slide

  51. PATTERN: ASYNC IN A LOOP
    async function runTasks(tasks) {
    var results = [];
    for (task of tasks) {
    results.push(await task.run());
    }
    return results;
    }

    View Slide

  52. THIS IS
    NOT ALL ROSES

    View Slide

  53. GOTCHA: YOU STILL HAVE TO CATCH ERRORS

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  59. GOTCHA: EASY TO OVERUSE AWAIT

    View Slide

  60. HOW DO YOU
    START USING THIS?

    View Slide

  61. Available in Chrome
    Behind a flag in Edge
    Coming in FireFox 52
    Implemented in WebKit

    View Slide

  62. View Slide

  63. THESE ARE OUR
    HIGH-LEVEL ASYNC CONSTRUCTS

    View Slide

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

    View Slide