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. 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; }
  2. 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); } }); }
  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; } 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); } }); }
  4. function getUser(name, callback) { var sql = 'SELECT * FROM

    users WHERE name=?'; query(sql, name, function (error, user) { // Nobody gets this return value! return user; }); }
  5. 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; }); }
  6. 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 }); }
  7. 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); } }); }
  8. 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); } } }); }); }
  9. 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); } }); }
  10. 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()); }
  11. 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()); }
  12. getUser('mjackson', function (error, user) { // ... }); // becomes

    getUser('mjackson').then(function (user) { // ... }, function (error) { // ... });
  13. var user = getUser('mjackson'); var name = user.name; // becomes

    getUser('mjackson').then(function (user) { return user.name; });
  14. 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; });
  15. try { deliverTweetTo(tweet, 'mjackson'); } catch (error) { handleError(error); }

    // becomes deliverTweetTo(tweet, 'mjackson') .then(undefined, handleError);
  16. 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); });
  17. 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); });
  18. 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);
  19. try { var user = getUser('mjackson'); var tweets = getNewTweets(user);

    updateTimeline(tweets); } catch (error) { handleError(error); }
  20. 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); }); } }); } });
  21. // 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
  22. 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);
  23. 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);
  24. 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; });
  25. function getNewTweetsForUsers(users) { return users.map(getNewTweets); } // becomes var q

    = require('q'); function getNewTweetsForUsers(users) { var promises = users.map(getNewTweets); return q.all(promises); }
  26. 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);