Understanding promises

@nolanlawson

no promise puns
We have a problem with promises article

Async code in PouchDB


db.get('my_doc_id').then(function(doc) {
  return db.remove(doc);
}).then(function (result) {
  return db.info();
}).then(function (info) {
  // yay I'm done
}).catch(function (err) {
  // ugh I got an error
});
          
Bad promises on Stack Overflow

What is the difference between these 4 promises?


doSomething().then(function () {
  return doSomethingElse();
});

doSomething().then(function () {
  doSomethingElse();
});

doSomething().then(doSomethingElse());

doSomething().then(doSomethingElse);

doSomething().then(function () {       // <- good pattern
  return doSomethingElse();
});

doSomething().then(function () {       // <- trap!
  doSomethingElse();
});

doSomething().then(doSomethingElse()); // <- trap!

doSomething().then(doSomethingElse);   // <- good pattern

Why do we need promises in JavaScript?

Java


public String readFile(String filename) throws IOException {
  String output = "";
  BufferedReader buff = new BufferedReader(
    new InputStreamReader(new FileInputStream(new File(filename))));
  while (buff.ready()) {
    output += buff.readLine() + "\n";
  }
  buff.close();
  return output;
}
          

Python


import requests;

response = requests.get('http://oursite.com/ourapi/');
json = response.json()
print json

          

JavaScript is single-threaded

latency numbers all programmers should know https://gist.github.com/hellerbarde/2843375

JavaScript


var xhr = new XMLHttpRequest();
xhr.onerror = function (err) {
  console.log('whoops, got an error', err);
};
xhr.onload = function (res) {
  console.log('woohoo, got a response', res);
};
xhr.open('GET', 'http://oursite.com/ourapi/');
          

JavaScript - jQuery ajax()


$.ajax("http://oursite.com/ourapi/", {
  success: function (res) {
    console.log("woohoo, got a response", res);
  },
  error: function (err) {
    console.log("whoops, got an error", err);
  }
});
console.log('firing request');
          

Node.js


var fs = require('fs');
fs.readFile('/tmp/myfile.txt', 'utf-8', function (err, text) {
  if (err) {
    // handle error
  } else {
    // handle success
  }
});
          
redemption from callback hell screenshot

The pyramid of doom


doSomething(function (err) {
  if (err) { return handleError(err); }
  doSomethingElse(function (err) {
    if (err) { return handleError(err); }
    doAnotherThing(function (err) {
      if (err) { return handleError(err); }
      // handle success
    });
  });
});
function handleError(err) {
  // handle error
}

doSomething(function (err) {
  if (err) { return handleError(err); }     // wait why is my code
  doSomethingElse(function (err) {
    if (err) { return handleError(err); }   // on a deathmarch
    doAnotherThing(function (err) {
      if (err) { return handleError(err); } // to the right of the screen
      // handle success
    });
  });
});
function handleError(err) {
  // handle error
}

Promises to the rescue!


doSomething().then(function (result) {
  // handle success
}).catch(function (err) {
  // handle error
});
                      

Promises to the rescue!


doSomething().then(function () {
  return doSomethingElse();
}).then(function () {
  return doAnotherThing();
}).then(function (result) {
  // yay, I'm done
}).catch(function (err) {
  // boo, I got an error
});

doSomething().then(function () {
  return doSomethingElse();        //        ________
}).then(function () {              //       |__MEOW__|
  return doAnotherThing();         //          \_/
}).then(function (result) {        //         /\_/\
  // yay, I'm done                 //    ____/ o o \
}).catch(function (err) {          //  /~____  =ø= /
  // boo, I got an error           // (______)__m_m)
});
                

Top 3 worst promise mistakes

1. The promisey pyramid of doom


doSomething().then(function () {
  doSomethingElse().then(function () {
    doAnotherThing().then(function () {
      // handle success
    }).catch(function (err) {
      // handle error
    });
  }).catch(function (err) {
    // handle error
  });
}).catch(function (err) {
  // handle error
});
                                

1. Solution: don't do that


doSomething().then(function () {
  return doSomethingElse();
}).then(function () {
  return doAnotherThing();
}).then(function (result) {
  // handle success
}).catch(function (err) {
  // handle error
});

2. Forgetting to catch()


playWithFire().then(function () {
  return liveDangerously();
}).then(function () {
  return betTheFarm();
}).then(function (result) {
  // handle success
}); // ← forgot to catch. any errors will be swallowed
          

2. Solution: add a catch()


playWithFire().then(function () {
  return liveDangerously();
}).then(function () {
  return betTheFarm();
}).then(function (result) {
  // handle success
}).catch(console.log.bind(console)); // ← this is badass
                    

3. Forgetting to return


goToTheATM().then(function () {
  grabMyCash();
}).then(function () {
  grabMyCard();
}).then(function (result) {
  // grabMyCash() and grabMyCard()
  // are not done yet!
}).catch(console.log.bind(console));
                                    

3. Solution: always return or throw inside a then() function


goToTheATM().then(function () {
  return grabMyCash();
}).then(function () {
  return grabMyCard();
}).then(function (result) {
  // yay, everything is done
}).catch(console.log.bind(console));
                                                                        

Promises - the one weird trick


someLibrary.doSomething().then(function () {
  // I'm inside a then() function!
});

Promises - the one weird trick


someLibrary.doSomething().then(function () {
  // 1) return another promise
  return someLibrary.doSomethingElse();

  // 2) return a synchronous value
  return {hooray: true};

  // 3) throw a synchronous error
  throw new Error('oh noes');
});

Promises - the one weird trick


db.get('user:nolan').then(function (user) {
  if (user.isLoggedOut()) {
    throw new Error('user logged out!');  // throwing a synchronous error!
  }
  if (inMemoryCache[user.id]) {
    return inMemoryCache[user.id];        // returning a synchronous value!
  }
  return db.get('account:' + user.id);    // returning a promise!
}).then(function (userAccount) {
  // I got a user account!
}).catch(function (err) {
  // Boo, I got an error!
});

OK, how do I use promises?

Promises with a capital P

  • Promises/A+ spec
  • ES6 Promises (same thing)

window.Promise in browsers

caniuse.com promises

global.Promise in Node/io.js

  • Node 0.10
  • Node 0.12
  • io.js

Promises in libraries

Node-style → Promise-style


var bluebird = require('bluebird');
var fs = bluebird.promisifyAll(require('fs'));

fs.readFileAsync('/tmp/myfile.txt', 'utf-8').then(function (text) {
  // handle success
}).catch(function (err) {
  // handle error
});
                  

Things that are not Promises ಠ_ಠ

  • async
  • jQuery promises
  • jQuery deferreds
  • Angular deferreds
  • Anything spelled "deferred"

Standard Promise APIs


var Promise = require('some-valid-library');

var promise = new Promise(function (resolve, reject) {
  // roll your own promise inside here
});

promise.then();
promise.catch();

Promise.resolve();
Promise.reject();
Promise.all();
Promise.race();
                    

Anti-footgun artillery

Uncaught error - silently ignored :(


Promise.resolve().then(function () {
  throw new Error("aw shucks");
});
                              
Node, io.js, and most browsers don't log this.

Uncaught error - warning logged! :)

Bluebird and Chrome native Promises show warnings.

io.js - unhandledRejection event

Async stacktraces in Chrome

Experimental Chrome dev tools

Experimental promises tab

Bluebird 3.0 warnings

bluebird warnings

Onward with ES7

With ES6 promises


var db = new PouchDB('mydb');

db.post({}).then(function (result) { // post a new doc
  return db.get(result.id);          // fetch the doc
}).then(function (doc) {
  console.log(doc);                  // log the doc
}).catch(function (err) {
  console.log(err);                  // log any errors
});
          

With ES7 async/await


let db = new PouchDB('mydb');

try {
  let result = await db.post({});
  let doc = await db.get(result.id);
  console.log(doc);
} catch (err) {
  console.log(err);
}
  

Promises are confusing.
ES7 will save us.

@nolanlawson

"We have a problem with promises"