ES5 Promises
Create a promise object, passing in a function that you want to execute, and two functions: resolve and reject.
var promise = new Promise(function(resolve, reject) {
});
In your callback, invoke either resolve
or reject
:
var promise = new Promise(function(resolve, reject) {
// do a thing, possibly async, then…
if (/* everything turned out fine */) {
resolve("Stuff worked!");
}
else {
reject(Error("It broke"));
}
});
Subsequent functions can be chained to your new promise
instance with .then()
:
promise.then(function(){
// on fulfilled
}, function() {
// on rejected
});
Transformation
The paramter you pass when you resolve
becomes the argument of the onFulfilled
handler:
var promise = new Promise(function(resolve, reject) {
resolve(1);
});
promise.then(function(val) {
console.log(val); // 1
return val + 2;
}).then(function(val) {
console.log(val); // 3
});
Error Handling
One way of adding an onRejected
handler:
promise.then(function(){
// on fulfilled
}, function() {
// on rejected
});
Also, you can chain the handler to catch()
:
promise.then(function(){
// on fulfilled
}).catch(function() {
// on rejected
});
All
We can combine both the worlds of async and sync with all
:
Promise.all(arrayOfPromises).then(function(arrayOfResults) {
//...
});
promise.then(function() {
// Take an array of promises and wait on them all
return Promise.all(
[array, of, promises]
);
}).then(function(){
// something done after all promises are resolved
});
API
Constructor
new Promise(function(resolve, reject) {});
resolve(thenable)
Your promise will be fulfilled/rejected with the outcome of thenable
resolve(obj)
Your promise is fulfilled with obj
reject(obj)
Your promise is rejected with obj. For consistency and debugging (eg stack traces), obj should be an instanceof Error. Any errors thrown in the constructor callback will be implicitly passed to reject().
Static Methods
Promise.resolve(promise);
Returns promise (only if promise.constructor == Promise)
Promise.resolve(thenable);
Make a new promise from the thenable. A thenable is promise-like in as far as it has a "then" method.
Promise.resolve(obj);
Make a promise that fulfills to obj. in this situation.
Promise.reject(obj);
Make a promise that rejects to obj. For consistency and debugging (e.g. stack traces), obj should be an instanceof Error.
Promise.all(array);
Make a promise that fulfills when every item in the array fulfills, and rejects if (and when) any item rejects. Each array item is passed to Promise.resolve, so the array can be a mixture of promise-like objects and other objects. The fulfillment value is an array (in order) of fulfillment values. The rejection value is the first rejection value.
Promise.race(array);
Make a Promise that fulfills as soon as any item fulfills, or rejects as soon as any item rejects, whichever happens first.
Instance Methods
promise.then(onFulfilled, onRejected)
onFulfilled is called when/if "promise" resolves. onRejected is called when/if "promise" rejects. Both are optional, if either/both are omitted the next onFulfilled/onRejected in the chain is called. Both callbacks have a single parameter, the fulfillment value or rejection reason. "then" returns a new promise equivalent to the value you return from onFulfilled/onRejected after being passed through Promise.resolve. If an error is thrown in the callback, the returned promise rejects with that error.
promise.catch(onRejected)
Sugar for promise.then(undefined, onRejected)
Example
For these examples, I've defined some logging as follows:
function printout(i) {
console.log("Executing Callback " + i + ": " + performance.now());
}
Then
We'd like to ensure 2 gets printed out after 1.
var promise = new Promise(function(resolve, reject) {
setTimeout(function(){
printout(1);
}, 1000);
});
promise.then(function(){
printout(2);
});
printout(3);
This fires off a timer, prints out 3, then prints out 1. It never prints out 2, because we never resolve our promise!
Resolving a Promise
var promise = new Promise(function(resolve, reject) {
setTimeout(function(){
printout(1);
}, 1000);
resolve();
});
promise.then(function(){
printout(2);
});
printout(3);
This fires off a timer, prints out 3, 2, 1. 2 is now printed, but it's not in order! That's because the timer is fired, the promise is resolved, 2 is printed while the timer is still running, then 1 is printed.
var promise = new Promise(function(resolve, reject) {
setTimeout(function(){
printout(1);
resolve();
}, 1000);
});
promise.then(function(){
printout(2);
});
printout(3);
We'll want to add the resolve to the function inside of the timeout to ensure 1 is printed before 2.
In all these cases, 3 is printed out first. It's not part of the promise chain.
Rejecting a Promise
If your promise is fulfilled, the onFulfilled
callback is executed.
Otherwise, the onRejected
callback is executed.
var promise = new Promise(function(resolve, reject) {
setTimeout(function(){
printout(1);
resolve(); // try changing to reject();
}, 1000);
});
promise.then(function(){
printout('2-onResolve');
}, function(){
printout('2-onReject');
});
printout(3);
Other
States and Fates: https://github.com/domenic/promises-unwrapping/blob/master/docs/states-and-fates.md
Polyfill
import { config, configure } from "./config";
import { objectOrFunction, isFunction, now } from './utils';
import { all } from "./all";
import { race } from "./race";
import { resolve as staticResolve } from "./resolve";
import { reject as staticReject } from "./reject";
import { asap } from "./asap";
var counter = 0;
config.async = asap; // default async is asap;
/**
* Promise constructor
*/
function Promise(resolver) {
if (!isFunction(resolver)) {
throw new TypeError('You must pass a resolver function as the first argument to the promise constructor');
}
if (!(this instanceof Promise)) {
throw new TypeError("Failed to construct 'Promise': Please use the 'new' operator, this object constructor cannot be called as a function.");
}
this._subscribers = [];
// pass this promise into the resolver
invokeResolver(resolver, this);
}
/**
* Invocations
*/
function invokeResolver(resolver, promise) {
function resolvePromise(value) {
// (a) resolve
resolve(promise, value);
}
function rejectPromise(reason) {
reject(promise, reason);
}
try {
// execute the resolver (arguments[0]), which effectively calls (a)
resolver(resolvePromise, rejectPromise);
} catch(e) {
rejectPromise(e);
}
}
function invokeCallback(settled, promise, callback, detail) {
var hasCallback = isFunction(callback),
value, error, succeeded, failed;
if (hasCallback) {
try {
value = callback(detail);
succeeded = true;
} catch(e) {
failed = true;
error = e;
}
} else {
value = detail;
succeeded = true;
}
if (handleThenable(promise, value)) {
return;
} else if (hasCallback && succeeded) {
resolve(promise, value);
} else if (failed) {
reject(promise, error);
} else if (settled === FULFILLED) {
resolve(promise, value);
} else if (settled === REJECTED) {
reject(promise, value);
}
}
var PENDING = void 0;
var SEALED = 0;
var FULFILLED = 1;
var REJECTED = 2;
function subscribe(parent, child, onFulfillment, onRejection) {
var subscribers = parent._subscribers;
var length = subscribers.length;
subscribers[length] = child;
subscribers[length + FULFILLED] = onFulfillment;
subscribers[length + REJECTED] = onRejection;
}
function publish(promise, settled) {
var child, callback,
subscribers = promise._subscribers,
detail = promise._detail;
for (var i = 0; i < subscribers.length; i += 3) {
child = subscribers[i];
callback = subscribers[i + settled];
invokeCallback(settled, child, callback, detail);
}
promise._subscribers = null;
}
Promise.prototype = {
constructor: Promise,
_state: undefined,
_detail: undefined,
_subscribers: undefined,
then: function(onFulfillment, onRejection) {
// create a promise for this and then
var promise = this;
var thenPromise = new this.constructor(function() {});
//
if (this._state) {
var callbacks = arguments;
config.async(function invokePromiseCallback() {
invokeCallback(promise._state, thenPromise, callbacks[promise._state - 1], promise._detail);
});
} else {
subscribe(this, thenPromise, onFulfillment, onRejection);
}
// return then
return thenPromise;
},
'catch': function(onRejection) {
return this.then(null, onRejection);
}
};
Promise.all = all;
Promise.race = race;
Promise.resolve = staticResolve;
Promise.reject = staticReject;
function handleThenable(promise, value) {
var then = null,
resolved;
try {
if (promise === value) {
throw new TypeError("A promises callback cannot return that same promise.");
}
if (objectOrFunction(value)) {
then = value.then;
if (isFunction(then)) {
then.call(value, function(val) {
if (resolved) { return true; }
resolved = true;
if (value !== val) {
resolve(promise, val);
} else {
fulfill(promise, val);
}
}, function(val) {
if (resolved) { return true; }
resolved = true;
reject(promise, val);
});
return true;
}
}
} catch (error) {
if (resolved) { return true; }
reject(promise, error);
return true;
}
return false;
}
function resolve(promise, value) {
if (promise === value) {
fulfill(promise, value);
} else if (!handleThenable(promise, value)) {
fulfill(promise, value);
}
}
function fulfill(promise, value) {
if (promise._state !== PENDING) { return; }
promise._state = SEALED;
promise._detail = value;
config.async(publishFulfillment, promise);
}
function reject(promise, reason) {
if (promise._state !== PENDING) { return; }
promise._state = SEALED;
promise._detail = reason;
config.async(publishRejection, promise);
}
function publishFulfillment(promise) {
publish(promise, promise._state = FULFILLED);
}
function publishRejection(promise) {
publish(promise, promise._state = REJECTED);
}
export { Promise };