Promises/A+
Understanding the spec through implementation
Rhys Brett-Bowen
Promises/A+
Understanding the spec through implementation
Promises - a quick intro
Rhys Brett-Bowen
server� .getName() // returns a promise
.then(0, logError) // log error� .then(0, function() {� return 'User'; // recovers error� }).then(displayName); // does something with future value
Promises/A+
Understanding the spec through implementation
Promises/A+
specification
Rhys Brett-Bowen
Promises/A+
Understanding the spec through implementation
V1.0 vs V1.1
Rhys Brett-Bowen
Promises/A+
Understanding the spec through implementation
Glossary
Fulfill(ed) - success state of a promise.
Reject(ed) - failed state of a promise.
Resolve - operation that uses a value to move a given promise in to a fulfilled or rejected state.
Thenable - same api as a promise (has a “then” method) and is assumed to work in similar ways.
Rhys Brett-Bowen
Promises/A+
Understanding the spec through implementation
2.1
States
Rhys Brett-Bowen
Promises/A+
Understanding the spec through implementation
2.1 Promise States
var State = {� PENDING: 0,� FULFILLED: 1,� REJECTED: 2�};
A promise must be in one of three states: pending, fulfilled, or rejected.
Rhys Brett-Bowen
Promises/A+
Understanding the spec through implementation
2.1 Promise States
2.1.1. When pending, a promise:
2.1.1.1. may transition to either the fulfilled or rejected state.
2.1.2. must not transition to any other state.
2.1.2.1. must not transition to any other state.
2.1.2.2. must have a value, which must not change.
2.1.3. When rejected, a promise:
2.1.3.1. must not transition to any other state.
2.1.3.2. must have a reason, which must not change.
pending
fulfilled
[[value]]
rejected
[[reason]]
transition(State.FULFILLED, value);
transition(State.REJECTED, reason);
Rhys Brett-Bowen
Promises/A+
Understanding the spec through implementation
2.1 Promise States (code)
var transition = function(state, value) {� � if (this.state == state || // must change the state� this.state !== State.PENDING || // can only change from pending� (state != State.FULFILLED && // can only change to fulfill or reject� state != state.REJECTED) ||� arguments.length < 2) { // must provide value/reason� return;� }� this.state = state; // change state� this.value = value;
this.run(); // we’ll see this later
};
Rhys Brett-Bowen
Promises/A+
Understanding the spec through implementation
2.2
Then
Rhys Brett-Bowen
Promises/A+
Understanding the spec through implementation
2.2 then (paraphrased)
2.2.1 onFulfilled & onRejected should be functions or ignored
2.2.2/3 onFulfilled / onRejected called only once after promise is fulfilled / rejected
2.2.4 !Important must be called after execution stack (asynchronously)
2.2.5 call onFulfilled / onRejected as contextless functions
2.2.6 then may be called multiple times and must execute in order called
2.2.7 then returns a new promise that should be resolved by running onFulfilled / onRejected or pass along the state if function not available
Rhys Brett-Bowen
Promises/A+
Understanding the spec through implementation
Anatomy of a theni
var p2 = p1.then(�// run when p1 fulfilled�function(<p1 value>) {� // code� return <resolve p2>; or throw <reject p2>;�},�// run when p1 rejected�function(<p1 reason>) {� // code� return <resolve p2>; or throw <reject p2>;�});
Rhys Brett-Bowen
Promises/A+
Understanding the spec through implementation
then (simple)
var then = function(onFulfilled, onRejected) {� // need to return a promise� var promise = new Promise();� this.queue.push({ // 2.2.6� fulfill : typeof onFulfilled == 'function' ?� onFulfilled : null, // 2.2.1� reject : typeof onRejected == 'function' ?� onRejected : null, // 2.2.1� promise: promise� });� this.run(); // We’ll see this later� return promise; // 2.2.7�};
Rhys Brett-Bowen
We’ll just put these functions in a queue and worry about them later...
Promises/A+
Understanding the spec through implementation
Run (actually run stuff)
var run = function() {� if (this.state == State.PENDING) return;� var self = this;� setTimeout(function() { // 2.2.4� while(self.queue.length) {� var obj = self.queue.shift(); // 2.2.6� try {� // resolve returned promise based on return� var fn = self.state == State.FULFILLED ? // 2.2.7� (obj.fulfill || function(x) {return x;}) :� (obj.reject || function(x) {throw x;});
resolve(obj.promise, fn(self.value)); // 2.2.5� } catch (e) {� // reject if an error is thrown� obj.promise.transition(State.REJECTED, e);� }� }� }, 0);�};
Rhys Brett-Bowen
Promises/A+
Understanding the spec through implementation
2.3
Resolve
Rhys Brett-Bowen
Promises/A+
Understanding the spec through implementation
2.3 [[resolve]](promise, x)
2.3.1 if promise and x refer to the same object, reject promise with a TypeError as the reason
var resolve = function(promise, x) {� if (promise === x) {� promise.transition(State.REJECTED, new TypeError());� }�};
NB: We must know the promise implementation to know how to reject it
Rhys Brett-Bowen
Promises/A+
Understanding the spec through implementation
2.3 [[resolve]](promise, x)
2.3.2 if x is a promise, adopt it’s state
var resolve = function(promise, x) {� // ...� if (x && x.constructor == Promise) { // 2.3.2� if (x.state == State.PENDING) { // 2.3.2.1� x.then(function(value) { // 2.3.2.2� promise.transition(State.FULFILLED, value);� }, function(reason) { // 2.3.2.3� promise.transition(State.REJECTED, reason);� });� } else {� promise.transition(x.state, x.value);� }� }�};
Rhys Brett-Bowen
Not really necessary as 2.3.3 will also cover
Promises/A+
Understanding the spec through implementation
2.3 [[resolve]](promise, x)
2.3.3 if x is an object or function - (typeof x == 'object' || typeof x == 'function') && x != null
var called = false; // 2.3.3.3.3�try {� var then = x.then; // 2.3.3.1� if (typeof then == 'function') { // 2.3.3.3� then.call(x, function(y) {� called = called || !resolve(promise, y); // 2.3.3.3.1� }, function(r) { // 2.3.3.3.2� called = called || !promise.transition(State.REJECTED, r);� });� } else { // 2.3.3.4� promise.transition(State.FULFILLED, x);� }�} catch(e) { // 2.3.3.2, 2.3.3.3.4� called || promise.transition(State.REJECTED, e);
}
Rhys Brett-Bowen
Promises/A+
Understanding the spec through implementation
Recursive
For asynchronous functions that rely on asynchronous functions that rely on asyc...
Rhys Brett-Bowen
var getName = function() {� return {then: function(fulfill) {� fulfill(getNameFromServer());� }};�};
�var greet = function(str) {� alert('Hi ' + str);�};
�var p = new Promise();�p.then(greet);�p.resolve(getName());
Promises/A+
Understanding the spec through implementation
2.3 [[resolve]](promise, x)
2.3.4 if x is not an object or function, fulfill promise with x
var resolve = function(promise, x) {� // ...� promise.transition(State.FULFILLED, x);�};
Rhys Brett-Bowen
Promises/A+
Understanding the spec through implementation
Our Promise
Rhys Brett-Bowen
Promises/A+
Understanding the spec through implementation
Our Promise
var Promise = function() {� this.state = State.PENDING;� this.queue = [];�};�Promise.prototype.transition = transition;
Promise.prototype.reject = function(r) {
this.transition(State.REJECTED, r);
};
Promise.prototype.resolve = function(v) {resolve(this, v);};�Promise.prototype.run = run;�Promise.prototype.then = then; // only API required
�// var p = new Promise();�// p.then(function(x) {alert(x)});�// Promise.resolve(p, 'Hello World');
Rhys Brett-Bowen
Promises/A+
Understanding the spec through implementation
Test it.
Rhys Brett-Bowen
Promises/A+
Understanding the spec through implementation
Some observations
Rhys Brett-Bowen
Promises/A+
Understanding the spec through implementation
Start resolution
var Promise = function(fn) {� // ...� fn(function(value) {� resolve(self, value);� }, function(reason) {� self.transition(State.REJECTED, reason);� });�};
// var p = new Promise( function(res, rej) { res(5); } );
Rhys Brett-Bowen
Promises/A+
Understanding the spec through implementation
Patterns
Rhys Brett-Bowen
Promises/A+
Understanding the spec through implementation
Promise Patterns - convert callback
var myFn = function(err, callback, args...);��Promise.convert = function(fn) {� return function() {� var ctx = this;� var args = [].slice.call(arguments);� return new Promise(function(res, rej) {� fn.apply(ctx, [rej, res].concat(args));� });�};
Use this to convert APIs that don’t use promises
(assumes callback only takes one argument)
Rhys Brett-Bowen
Promises/A+
Understanding the spec through implementation
Promise Patterns - some setup
var asyncAddOne = Promise.convert(function(err, cb, val) {� setTimeout(function() { cb(val + 1); });�});
�var asyncMultiplyTwo = function(val) {� return new Promise(function(res, rej) {� setTimeout(function() { res(val * 2); });� });�});
�var thrower = function(num) {� throw num;�};
�var alertResult = function(value) { alert(value); };
Rhys Brett-Bowen
Promises/A+
Understanding the spec through implementation
Promise Patterns - pass along error
asyncAddOne(1)� .then(asyncMultiplyTwo)� .then(thrower)� .then(alertResult)� .then(0, function(err) {� console.log(err);� });
Rhys Brett-Bowen
Promises/A+
Understanding the spec through implementation
Promise Patterns - fix error
asyncAddOne(0)� .then(asyncMultiplyTwo)� .then(thrower)
.then(0, function() {
return 0;
})� .then(alertResult);
Rhys Brett-Bowen
Promises/A+
Understanding the spec through implementation
Promise Patterns - pool
Promise.pool = function() {� var promises = [].slice.call(arguments);� var promise = new Promise(function(res, rej) {� var count = promises.length, results = [];� promises.forEach(function(p,i) { // all promises� p.then(function(data) {� results[i] = data; // save results� if(!--count) res(results); // until all done� }, function(err){� rej({index: i, err: err}); // error� });� });� });� return promise;�};
// var name = getName().then(0,function(){return “USER”;});
// Promise.pool(name, getAddress()).then(display);
Rhys Brett-Bowen
Promises/A+
Understanding the spec through implementation
Questions?
Rhys Brett-Bowen
Google - Software Engineer
https://github.com/rhysbrettbowen
https://plus.google.com/109445215696684436299
Rhys Brett-Bowen
Promises/A+
Understanding the spec through implementation