1 of 32

Promises/A+

Understanding the spec through implementation

Rhys Brett-Bowen

@RhysBB

Promises/A+

Understanding the spec through implementation

2 of 32

Promises - a quick intro

  • A promise represents a future value.
  • Operations can be attached to that value.
  • Errors can be handled and recovered from.

Rhys Brett-Bowen

@RhysBB

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

3 of 32

Promises/A+

specification

  • Based on CommonJS Promises/A proposal
  • Moved to v1.1 in August 2013
  • Covers resolution but doesn’t cover how to start
  • Specification !== Implementation

Rhys Brett-Bowen

@RhysBB

Promises/A+

Understanding the spec through implementation

4 of 32

V1.0 vs V1.1

  • Better explanations
  • New recursive resolution method
  • allows recognizing own implementation (3.4)

Rhys Brett-Bowen

@RhysBB

Promises/A+

Understanding the spec through implementation

5 of 32

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

@RhysBB

Promises/A+

Understanding the spec through implementation

6 of 32

2.1

States

Rhys Brett-Bowen

@RhysBB

Promises/A+

Understanding the spec through implementation

7 of 32

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

@RhysBB

Promises/A+

Understanding the spec through implementation

8 of 32

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

@RhysBB

Promises/A+

Understanding the spec through implementation

9 of 32

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/reasonreturn;� }� this.state = state; // change state� this.value = value;

this.run(); // we’ll see this later

};

Rhys Brett-Bowen

@RhysBB

Promises/A+

Understanding the spec through implementation

10 of 32

2.2

Then

Rhys Brett-Bowen

@RhysBB

Promises/A+

Understanding the spec through implementation

11 of 32

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

@RhysBB

Promises/A+

Understanding the spec through implementation

12 of 32

Anatomy of a theni

var p2 = p1.then(�// run when p1 fulfilled�function(<p1 value>) {� // codereturn <resolve p2>; or throw <reject p2>;�},�// run when p1 rejected�function(<p1 reason>) {� // codereturn <resolve p2>; or throw <reject p2>;�});

Rhys Brett-Bowen

@RhysBB

Promises/A+

Understanding the spec through implementation

13 of 32

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 laterreturn promise; // 2.2.7�};

Rhys Brett-Bowen

@RhysBB

We’ll just put these functions in a queue and worry about them later...

Promises/A+

Understanding the spec through implementation

14 of 32

Run (actually run stuff)

var run = function() {� if (this.state == State.PENDING) return;� var self = this;� setTimeout(function() { // 2.2.4while(self.queue.length) {� var obj = self.queue.shift(); // 2.2.6try {� // 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

@RhysBB

Promises/A+

Understanding the spec through implementation

15 of 32

2.3

Resolve

Rhys Brett-Bowen

@RhysBB

Promises/A+

Understanding the spec through implementation

16 of 32

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

@RhysBB

Promises/A+

Understanding the spec through implementation

17 of 32

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.2if (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

@RhysBB

Not really necessary as 2.3.3 will also cover

Promises/A+

Understanding the spec through implementation

18 of 32

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.3try {� var then = x.then; // 2.3.3.1if (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

@RhysBB

Promises/A+

Understanding the spec through implementation

19 of 32

Recursive

For asynchronous functions that rely on asynchronous functions that rely on asyc...

Rhys Brett-Bowen

@RhysBB

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

20 of 32

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

@RhysBB

Promises/A+

Understanding the spec through implementation

21 of 32

Our Promise

Rhys Brett-Bowen

@RhysBB

Promises/A+

Understanding the spec through implementation

22 of 32

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

@RhysBB

Promises/A+

Understanding the spec through implementation

23 of 32

Test it.

Rhys Brett-Bowen

@RhysBB

Promises/A+

Understanding the spec through implementation

24 of 32

Some observations

  • Promises are about a single value/reason.
  • Always throw/return from onFulfilled/onRejected.
  • Only rely on then method unless you know (and control) the implementation.
  • Promise creator controls resolution.

Rhys Brett-Bowen

@RhysBB

Promises/A+

Understanding the spec through implementation

25 of 32

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); } );

  • Upcoming DOM promises:

Rhys Brett-Bowen

@RhysBB

Promises/A+

Understanding the spec through implementation

26 of 32

Patterns

Rhys Brett-Bowen

@RhysBB

Promises/A+

Understanding the spec through implementation

27 of 32

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

@RhysBB

Promises/A+

Understanding the spec through implementation

28 of 32

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

@RhysBB

Promises/A+

Understanding the spec through implementation

29 of 32

Promise Patterns - pass along error

asyncAddOne(1)� .then(asyncMultiplyTwo)� .then(thrower)� .then(alertResult)� .then(0, function(err) {� console.log(err);� });

Rhys Brett-Bowen

@RhysBB

Promises/A+

Understanding the spec through implementation

30 of 32

Promise Patterns - fix error

asyncAddOne(0)� .then(asyncMultiplyTwo)� .then(thrower)

.then(0, function() {

return 0;

})� .then(alertResult);

Rhys Brett-Bowen

@RhysBB

Promises/A+

Understanding the spec through implementation

31 of 32

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 resultsif(!--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

@RhysBB

Promises/A+

Understanding the spec through implementation

32 of 32

Questions?

Rhys Brett-Bowen

@RhysBB

Promises/A+

Understanding the spec through implementation