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 full-size slide

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

    View full-size slide

  3. LET'S TALK ABOUT
    JavaScript

    View full-size slide

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

    View full-size slide

  5. JavaScript
    REMOVES ASYNC CHALLENGES

    View full-size slide

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

    View full-size slide

  7. WE HAVE AN
    ASYNCHRONOUS
    ECOSYSTEM

    View full-size slide

  8. SIMPLER
    BUT NOT SIMPLE

    View full-size slide

  9. JavaScript
    CAN'T DIRECTLY EXPRESS ASYNC

    View full-size slide

  10. WE NEED
    HIGH-LEVEL ASYNC CONSTRUCTS

    View full-size slide

  11. 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 full-size slide

  12. 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 full-size slide

  13. 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 full-size slide

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

    View full-size slide

  15. 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 full-size slide

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

    View full-size slide

  17. 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 full-size slide

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

    View full-size slide

  19. PROMISES ARE OUR
    CURRENT BEST SOLUTION

    View full-size slide

  20. PROMISES STILL HAVE
    TOO MUCH CEREMONY

    View full-size slide

  21. PROMISES STILL
    COUPLE FUNCTION SCOPES
    WITH ASYNC OPERATIONS

    View full-size slide

  22. 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 full-size slide

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

    View full-size slide

  24. 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 full-size slide

  25. 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 full-size slide

  26. 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 full-size slide

  27. 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 full-size slide

  28. PROMISES GIVE US A
    MODEL FOR ASYNC
    PROCESSES

    View full-size slide

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

    View full-size slide

  30. GENERATORS ARE FUNCTIONS THAT
    REPEATEDLY YIELD VALUES

    View full-size slide

  31. GENERATORS ARE FUNCTIONS THAT
    PAUSE

    View full-size slide

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

    View full-size slide

  33. 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 full-size slide

  34. 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 full-size slide

  35. 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 full-size slide

  36. Promises
    +
    Generators

    View full-size slide

  37. 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 full-size slide

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

    View full-size slide

  39. 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 full-size slide

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

    View full-size slide

  41. PROMISES ARE DEAD
    LONG LIVE PROMISES

    View full-size slide

  42. Async / Await

    View full-size slide

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

    View full-size slide

  44. 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 full-size slide

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

    View full-size slide

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

    View full-size slide

  47. THIS IS
    NOT ALL ROSES

    View full-size slide

  48. GOTCHA: YOU STILL HAVE TO CATCH ERRORS

    View full-size slide

  49. 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 full-size slide

  50. 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 full-size slide

  51. 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 full-size slide

  52. 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 full-size slide

  53. 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 full-size slide

  54. GOTCHA: EASY TO OVERUSE AWAIT

    View full-size slide

  55. HOW DO YOU
    START USING THIS?

    View full-size slide

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

    View full-size slide

  57. THESE ARE OUR
    HIGH-LEVEL ASYNC CONSTRUCTS

    View full-size slide

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

    View full-size slide