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

Redemption from Callback Hell

Redemption from Callback Hell

A strategy for avoiding messy callback APIs in JavaScript using Promises. Originally given on April 1, 2013 at HTML5 DevConf in San Francisco, CA.

Michael Jackson

April 01, 2013
Tweet

More Decks by Michael Jackson

Other Decks in Programming

Transcript

  1. REDEMPTION
    FROM
    CALLBACK HELL

    View full-size slide

  2. ASYNCHRONOUS
    LET’S TALK ABOUT
    JAVASCRIPT APIs

    View full-size slide

  3. function getUser(name) {
    var sql = 'SELECT * FROM users WHERE name=?';
    var user = query(sql, name); // <- blocking
    if (!user) throw new Error('no user!');
    return user;
    }

    View full-size slide

  4. function getUser(name, callback) {
    var sql = 'SELECT * FROM users WHERE name=?';
    query(sql, name, function (error, user) {
    if (error) {
    callback(error);
    } else if (!user) {
    callback(new Error('no user!'));
    } else {
    callback(null, user);
    }
    });
    }

    View full-size slide

  5. function getUser(name) {
    var sql = 'SELECT * FROM users WHERE name=?';
    var user = query(sql, name); // <- blocking
    if (!user) throw new Error('no user!');
    return user;
    }
    function getUser(name, callback) {
    var sql = 'SELECT * FROM users WHERE name=?';
    query(sql, name, function (error, user) {
    if (error) {
    callback(error);
    } else if (!user) {
    callback(new Error('no user!'));
    } else {
    callback(null, user);
    }
    });
    }

    View full-size slide

  6. NO RETURN
    IN A CALLBACK, THERE IS

    View full-size slide

  7. function getUser(name, callback) {
    var sql = 'SELECT * FROM users WHERE name=?';
    query(sql, name, function (error, user) {
    // Nobody gets this return value!
    return user;
    });
    }

    View full-size slide

  8. NO THROW
    IN A CALLBACK, THERE IS

    View full-size slide

  9. function getUser(name, callback) {
    var sql = 'SELECT * FROM users WHERE name=?';
    query(sql, name, function (error, user) {
    // Nobody can catch this error!
    if (error) throw error;
    });
    }

    View full-size slide

  10. function getUser(name, callback) {
    var sql = 'SELECT * FROM users WHERE name=?';
    query(sql, name, function (error, user) {
    // Nobody can catch this error!
    if (error) throw error; // <- BAD
    });
    }

    View full-size slide

  11. • NO RETURN
    • NO THROW

    View full-size slide

  12. function getUser(name) {
    var sql = 'SELECT * FROM users WHERE name=?';
    var user = query(sql, name); // <- blocking
    if (!user) throw new Error('no user!');
    return user;
    }
    function getUser(name, callback) {
    var sql = 'SELECT * FROM users WHERE name=?';
    query(sql, name, function (error, user) {
    if (error) {
    callback(error);
    } else if (!user) {
    callback(new Error('no user!'));
    } else {
    callback(null, user);
    }
    });
    }

    View full-size slide

  13. NO STACK
    IN A CALLBACK, THERE IS

    View full-size slide

  14. NO GUARANTEES
    USING CALLBACKS, THERE ARE

    View full-size slide

  15. function getUsers(names, callback) {
    var sql = 'SELECT * FROM users WHERE name=?';
    var users = [];
    names.forEach(function (name) {
    query(sql, name, function (error, user) {
    if (error) {
    callback(error);
    } else if (!user) {
    callback(new Error('no user!'));
    } else {
    users.push(user);
    if (users.length === names.length) {
    callback(null, users);
    }
    }
    });
    });
    }

    View full-size slide

  16. • NO STACK (NO RETURN/THROW)
    • NO GUARANTEES
    • BUT AT LEAST WE DON’T BLOCK!?

    View full-size slide

  17. function getUser(name) {
    var sql = 'SELECT * FROM users WHERE name=?';
    var user = query(sql, name); // <- blocking
    if (!user) throw new Error('no user!');
    return user;
    }
    function getUser(name, callback) {
    var sql = 'SELECT * FROM users WHERE name=?';
    query(sql, name, function (error, user) {
    if (error) {
    callback(error);
    } else if (!user) {
    callback(new Error('no user!'));
    } else {
    callback(null, user);
    }
    });
    }

    View full-size slide

  18. ES6 GENERATORS
    THE FUTURE IS

    View full-size slide

  19. function fibonacci() {
    var i = 0, j = 1;
    while (true) {
    yield i;
    var t = i;
    i = j;
    j += t;
    }
    }
    var generator = fibonacci();
    for (var i = 0; i < 12; i++) {
    print(generator.next());
    }

    View full-size slide

  20. function fibonacci() {
    var i = 0, j = 1;
    while (true) {
    yield i; // <- awesome
    var t = i;
    i = j;
    j += t;
    }
    }
    var generator = fibonacci();
    for (var i = 0; i < 12; i++) {
    print(generator.next());
    }

    View full-size slide

  21. • FIRST-CLASS COROUTINES
    • SUSPEND EXECUTION CONTEXT
    • SMALL API
    • NOT AVAILABLE YET

    View full-size slide

  22. • DISCOVERED CIRCA 1989
    • INSPIRED BY E
    • WIDELY USED OUTSIDE JS

    View full-size slide

  23. Horrible Lies!!!

    View full-size slide

  24. https://github.com/promises-aplus/promises-spec

    View full-size slide

  25. • Q
    • RSVP
    • when
    https://github.com/promises-aplus/promises-spec/blob/master/implementations.md

    View full-size slide

  26. ASYNCHRONOUS
    A PROMISE IS AN
    VALUE

    View full-size slide

  27. getUser('mjackson', function (error, user) {
    // ...
    });
    // becomes
    getUser('mjackson').then(function (user) {
    // ...
    }, function (error) {
    // ...
    });

    View full-size slide

  28. TRANSFORMS
    THERE ARE 4 BASIC SYNC TO ASYNC

    View full-size slide

  29. var user = getUser('mjackson');
    var name = user.name;
    // becomes
    getUser('mjackson').then(function (user) {
    return user.name;
    });

    View full-size slide

  30. var user = getUser('mjackson');
    if (!user) throw new Error('no user!');
    var name = user.name;
    // becomes
    getUser('mjackson').then(function (user) {
    if (!timeline) throw new Error('no user!');
    return user.name;
    });

    View full-size slide

  31. try {
    deliverTweetTo(tweet, 'mjackson');
    } catch (error) {
    handleError(error);
    }
    // becomes
    deliverTweetTo(tweet, 'mjackson')
    .then(undefined, handleError);

    View full-size slide

  32. try {
    var user = getUser('mjackson');
    } catch (error) {
    throw new Error('ERROR: ' + error.message);
    }
    // becomes
    getUser('mjackson').then(undefined, function (error) {
    throw new Error('ERROR: ' + error.message);
    });

    View full-size slide

  33. try {
    var user = getUser('mjackson');
    } catch (error) {
    throw new Error('ERROR: ' + error.message);
    }
    // becomes
    getUser('mjackson').then(undefined, function (error) {
    throw new Error('ERROR: ' + error.message);
    });

    View full-size slide

  34. •STACK SEMANTICS
    •GUARANTEES

    View full-size slide

  35. SEQUENCE
    OPERATIONS IN

    View full-size slide

  36. var user = getUser('mjackson');
    var tweets = getNewTweets(user);
    updateTimeline(tweets);
    // using callbacks
    getUser('mjackson', function (user) {
    getNewTweets(user, function (tweets) {
    updateTimeline(tweets);
    });
    });
    // using promises
    getUser('mjackson')
    .then(getNewTweets)
    .then(updateTimeline);

    View full-size slide

  37. EXCEPTIONS
    HANDLING

    View full-size slide

  38. try {
    var user = getUser('mjackson');
    var tweets = getNewTweets(user);
    updateTimeline(tweets);
    } catch (error) {
    handleError(error);
    }

    View full-size slide

  39. getUser('mjackson', function (error, user) {
    if (error) {
    handleError(error);
    } else {
    getNewTweets(user, function (error, tweets) {
    if (error) {
    handleError(error);
    } else {
    updateTimeline(tweets, function (error) {
    if (error) handleError(error);
    });
    }
    });
    }
    });

    View full-size slide

  40. // Same example, complete with node.js time bombs!
    getUser('mjackson', function (error, user) {
    if (error) throw error;
    getNewTweets(user, function (error, tweets) {
    if (error) throw error;
    updateTimeline(tweets, function (error) {
    if (error) throw error;
    });
    });
    });
    process.on('uncaughtException', handleError); // LOL

    View full-size slide

  41. try {
    var user = getUser('mjackson');
    var tweets = getNewTweets(user);
    updateTimeline(tweets);
    } catch (error) {
    handleError(error);
    }
    // becomes
    getUser('mjackson')
    .then(getNewTweets)
    .then(updateTimeline)
    .then(undefined, handleError);

    View full-size slide

  42. FUD
    DISPEL THE

    View full-size slide

  43. var fs = require('fs');
    var q = require('q');
    var readFile = q.denodeify(fs.readFile);
    var promise = readFile(__filename, 'utf8');
    promise.then(console.log, console.error);

    View full-size slide

  44. function createUser(userName, userData, callback) {
    return database.ensureUserNameNotTaken(userName)
    .then(function () {
    database.saveUserData(userName, userData);
    })
    .nodeify(callback);
    }

    View full-size slide

  45. •PROMISES COMING TO THE DOM
    •GENERATORS COMING TO V8
    •PROMISES ARE FUTURE-PROOF

    View full-size slide

  46. var q = require('q');
    var getUser = q.async(function * (name) {
    var sql = 'SELECT * FROM users WHERE name=?';
    var user = yield query(sql, name);
    if (!user) throw new Error('no user!');
    return user;
    });

    View full-size slide

  47. PARALLEL
    EXECUTE OPERATIONS IN

    View full-size slide

  48. function getNewTweetsForUsers(users) {
    return users.map(getNewTweets);
    }
    // becomes
    var q = require('q');
    function getNewTweetsForUsers(users) {
    var promises = users.map(getNewTweets);
    return q.all(promises);
    }

    View full-size slide

  49. SERVERS
    BUILD FAULT-TOLERANT

    View full-size slide

  50. var http = require('http');
    var q = require('q');
    var server = http.createServer(function (req, res) {
    handleRequest(req).then(function (response) {
    res.writeHead(response.status, response.headers);
    res.end(response.content);
    }, function (error) {
    console.error(error);
    res.writeHead(500, { 'Content-Type': 'text/plain' });
    res.end('Internal Server Error');
    });
    });
    server.listen(3000);

    View full-size slide

  51. https://github.com/machjs/mach

    View full-size slide