Skip to content

Instantly share code, notes, and snippets.

Created Aug 31, 2013
Embed
What would you like to do?
An implementation of AP2
// This module uses AP2 as its starting point:
// https://github.com/domenic/promises-unwrapping
var hasOwn = Function.prototype.call.bind(Object.prototype.hasOwnProperty);
function mixin(a, b) {
Object.keys(b).forEach(function(key) {
a[key] = b[key];
});
return a;
}
function defer(f) {
setTimeout(f, 0);
}
function NO_RESOLVER() { };
function Promise(resolver) {
// 1. Let promise be the this value.
var promise = this;
var resolve, reject;
var threw, e;
// 2. If Type(promise) is not Object, throw a TypeError exception.
if (Object(promise) !== promise) {
throw new TypeError('Expected object');
}
// 3. If promise.[[IsPromise]] is unset, then throw a TypeError exception.
if (!('#is_promise' in promise)) {
throw new TypeError('Expected Promise');
}
// 4. If promise.[[IsPromise]] is not undefined, then throw a TypeError exception.
if (promise['#is_promise'] !== undefined) {
throw new TypeError('Promise has already been initialized');
}
// 5. If Type(resolver) is not Function, then throw a TypeError exception.
if (typeof resolver != 'function') {
throw new TypeError('Function expected');
}
// 6. Set promise.[[IsPromise]] to true.
promise['#is_promise'] = true;
promise['#derived'] = [ ];
if (resolver !== NO_RESOLVER) {
// 7. Let resolve(x) be an ECMAScript function that calls Resolve(promise, x).
resolve = function(x) { Resolve(promise, x); };
// 8. Let reject(r) be an ECMAScript function that calls Reject(promise, r).
reject = function(r) { Reject(promise, r); };
// 9. Call resolver.[[Call]](undefined, [resolve, reject]).
try {
resolver.call(undefined, resolve, reject);
} catch(x) {
threw = true;
e = x;
}
// 10. If calling the function throws an exception e,
if (threw) {
// call Reject(promise, e).
Reject(promise, e);
}
}
// 11. Return promise.
return promise;
}
mixin(Promise.prototype, {
'#is_promise': undefined,
'#following': undefined,
'#value': undefined,
'#reason': undefined,
'#derived': undefined,
then: function then(onfulfilled, onrejected) {
// 1. If IsPromise(this) is false, throw a TypeError.
if (!IsPromise(this)) {
throw new TypeError('Promise expected');
}
// 2. Otherwise, return Then(this, onFulfilled, onRejected)
return Then(this, onfulfilled, onrejected);
},
catch: function catch_(onrejected) {
// 1. If IsPromise(this) is false, throw a TypeError.
if (!IsPromise(this)) {
throw new TypeError('Promise expected');
}
// 2. Otherwise, return Then(this, undefined, onRejected).
return Then(this, undefined, onrejected);
}
});
Promise.resolve = function resolve(x) {
// 1. Let p be a newly-created promise.
var p = CreatePromise();
// 2. Call Resolve(p, x).
Resolve(p, x);
// 3. Return p.
return p;
};
Promise.reject = function reject(r) {
// 1. Let p be a newly-created promise.
var p = CreatePromise();
// 2. Call Reject(p, r).
Reject(p, r);
// 3. Return p.
return p;
};
Promise.from = function from(x) {
var p;
// 1. If IsPromise(x), return x.
if (IsPromise(x)) {
return x;
// 2. Otherwise,
} else {
// 1. Let p be a newly-created promise.
p = CreatePromise();
// 2. Call Resolve(p, x).
Resolve(p, x);
// 3. Return p.
return p;
}
};
function IsPromise(x) {
// 1. Return true if IsObject(x) and x.[[IsPromise]] is true.
// 2. Otherwise, return false.
return Object(x) === x && x['#is_promise'] === true;
}
function Resolve(p, x) {
// 1. If p.[[Following]], p.[[Value]], or p.[[Reason]] are set, terminate these steps.
if (hasOwn(p, '#following') || hasOwn(p, '#value') || hasOwn(p, '#reason')) {
return;
}
// 2. If IsPromise(x),
if (IsPromise(x)) {
// 1. If SameValue(p, x),
if (p === x) {
// 1. Let selfResolutionError be a newly-created TypeError object.
// 2. Call SetReason(p, selfResolutionError).
SetReason(new TypeError());
// 2. Otherwise, if x.[[Following]] is set,
} else if (hasOwn(x, '#following')) {
// 1. Let p.[[Following]] be x.[[Following]].
p['#following'] = x['#following'];
// 2. Add { [[DerivedPromise]]: p, [[OnFulfilled]]: undefined, [[OnRejected]]: undefined } to x.[[Following]].[[Derived]].
x['#following']['#derived'].push({ derived_promise: p, onfulfilled: undefined, onrejected: undefined });
// 3. Otherwise, if x.[[Value]] is set,
} else if (hasOwn(x, '#value')) {
// call SetValue(p, x.[[Value]]).
SetValue(p, x['#value']);
// 4. Otherwise, if x.[[Reason]] is set,
} else if (hasOwn(x, '#reason')) {
// call SetReason(p, x.[[Reason]]).
SetReason(p, x['#reason']);
// 5. Otherwise,
} else {
// 1. Let p.[[Following]] be x.
p['#following'] = x;
// 2. Add { [[DerivedPromise]]: p, [[OnFulfilled]]: undefined, [[OnRejected]]: undefined } to x.[[Derived]].
x['derived'].push({ derived_promise: p, onfulfilled: undefined, onrejected: undefined });
}
// 3. Otherwise,
} else {
// call SetValue(p, x).
SetValue(p, x);
}
}
function Reject(p, r) {
// 1. If p.[[Following]], p.[[Value]], or p.[[Reason]] are set, terminate these steps.
if (hasOwn(p, '#following') || hasOwn(p, '#value') || hasOwn(p, '#reason')) {
return;
}
// 2. Call SetReason(p, r).
SetReason(p, r);
}
function Then(p, onfulfilled, onrejected) {
var q, derived;
// 1. If p.[[Following]] is set,
if (hasOwn(p, '#following')) {
// 1. Return Then(p.[[Following]], onFulfilled, onRejected).
return Then(p['#following'], onfulfilled, onrejected);
// 2. Otherwise,
} else {
// 1. Let q be a new promise.
q = CreatePromise();
// 2. Let derived be { [[DerivedPromise]]: q, [[OnFulfilled]]: onFulfilled, [[OnRejected]]: onRejected }.
derived = { derived_promise: q, onfulfilled: onfulfilled, onrejected: onrejected };
// 3. If p.[[Value]] or p.[[Reason]] is set,
if (hasOwn(p, '#value') || hasOwn(p, '#reason')) {
// call UpdateDerived(derived, p).
UpdateDerived(derived, p);
// 4. Otherwise,
} else {
// add derived to p.[[Derived]].
p['#derived'].push(derived);
}
// 5. Return q.
return q;
}
}
function PropagateToDerived(p) {
// 1. Assert: exactly one of p.[[Value]] or p.[[Reason]] is set.
if (hasOwn(p, '#value') + hasOwn(p, '#reason') != 1) {
throw new Error('Expected exactly one of p.[[Value]] or p.[[Reason]] to be set.');
}
var derived = p['#derived'];
// 2. For each derived promise transform derived in p.[[Derived]],
for (var i = 0; i < derived.length; i++) {
// 1. Call UpdateDerived(derived, p).
UpdateDerived(derived[i], p);
}
// 3. Clear p.[[Derived]].
p['#derived'] = [ ];
}
function UpdateDerived(derived, originator) {
// 1. Assert: exactly one of originator.[[Value]] or originator.[[Reason]] is set.
if (hasOwn(originator, '#value') + hasOwn(originator, '#reason') != 1) {
throw new Error('Expected exactly one of originator.[[Value]] or originator.[[Reason]] to be set.');
}
var value;
defer(function() {
// 2. If originator.[[Value]] is set,
if (hasOwn(originator, '#value')) {
value = originator['#value'];
// 1. If IsObject(originator.[[Value]]),
if (Object(value) === value) {
// queue a microtask to run the following:
var then, coerced, threw = false, e;
// 1. Let then be Get(originator.[[Value]], "then")
try {
then = value.then;
} catch(x) {
threw = true;
e = x;
}
// 2. If retrieving the property throws an exception e,
if (threw) {
// call UpdateDerivedFromReason(derived, e).
UpdateDerivedFromReason(derived, e);
// 3. Otherwise, if Type(then) is Function,
} else if (typeof then == 'function') {
// 1. Let coerced be CoerceThenable(originator.[[Value]], then).
coerced = CoerceThenable(value, then);
// 2. If coerced.[[Value]] or coerced.[[Reason]] is set,
if (hasOwn(coerced, '#value') || hasOwn(coerced, '#reason')) {
// call UpdateDerived(derived, coerced).
UpdateDerived(derived, coerced);
// 3. Otherwise,
} else {
// add derived to coerced.[[Derived]].
coerced['#derived'].push(derived);
}
// 4. Otherwise,
} else {
// call UpdateDerivedFromValue(derived, originator.[[Value]]).
UpdateDerivedFromValue(derived, value);
}
// 2. Otherwise,
} else {
// call UpdateDerivedFromValue(derived, originator.[[Value]]).
UpdateDerivedFromValue(derived, value);
}
// 3. Otherwise,
} else {
// call UpdateDerivedFromReason(derived, originator.[[Reason]]).
UpdateDerivedFromReason(derived, originator['#reason']);
}
});
}
function UpdateDerivedFromValue(derived, value) {
// 1. If IsCallable(derived.[[OnFulfilled]]),
if (hasOwn(derived, 'onfulfilled') && typeof derived.onfulfilled == 'function') {
// call CallHandler(derived.[[DerivedPromise]], derived.[[OnFulfilled]], value).
CallHandler(derived.derived_promise, derived.onfulfilled, value);
// 2. Otherwise,
} else {
// call SetValue(derived.[[DerivedPromise]], value).
SetValue(derived.derived_promise, value);
}
}
function UpdateDerivedFromReason(derived, reason) {
// 1. If IsCallable(derived.[[OnRejected]]),
if (hasOwn(derived, 'onrejected') && typeof derived.onrejected == 'function') {
// call CallHandler(derived.[[DerivedPromise]], derived.[[OnRejected]], reason).
CallHandler(derived.derived_promise, derived.onrejected, reason);
// 2. Otherwise,
} else {
// call SetReason(derived.[[DerivedPromise]], reason).
SetReason(derived.derived_promise, reason);
}
}
function CallHandler(derived_promise, handler, argument) {
var v, threw = false, e;
// 1. Let v be handler(argument).
try {
v = handler(argument);
} catch(x) {
threw = true;
e = x;
}
// 2. If this call throws an exception e,
if (threw) {
// call Reject(derivedPromise, e).
Reject(derived_promise, e);
// 3. Otherwise,
} else {
// call Resolve(derivedPromise, v).
Resolve(derived_promise, v);
}
}
function SetValue(p, value) {
// 1. Assert: neither p.[[Value]] nor p.[[Reason]] are set.
if (hasOwn(p, '#value') || hasOwn(p, '#reason')) {
throw new Error('Expected neither p.[[Value]] nor p.[[Reason]] to be set');
}
// 2. Set p.[[Value]] to value.
p['#value'] = value;
// 3. Unset p.[[Following]].
delete p['#following'];
// 4. Call PropagateToDerived(p).
PropagateToDerived(p);
}
function SetReason(p, reason) {
// 1. Assert: neither p.[[Value]] nor p.[[Reason]] are set.
if (hasOwn(p, '#value') || hasOwn(p, '#reason')) {
throw new Error('Expected neither p.[[Value]] nor p.[[Reason]] to be set');
}
// 2. Set p.[[Reason]] to reason.
p['#reason'] = reason;
// 3. Unset p.[[Following]].
delete p['#following'];
// 4. Call PropagateToDerived(p).
PropagateToDerived(p);
}
function CoerceThenable(thenable, then) {
var threw = false, e;
// 1. Assert: IsObject(thenable).
if (Object(thenable) !== thenable) {
throw new TypeError('Object expected');
}
// 2. Assert: IsCallable(then).
if (typeof then != 'function') {
throw new TypeError('Function expected');
}
// 3. Assert: the execution context stack is empty.
// TODO: ?
// 4. Let p be a new promise.
p = CreatePromise();
// 5. Let resolve(x) be an ECMAScript function that calls Resolve(p, x).
var resolve = function(x) { Resolve(p, x); };
// 6. Let reject(r) be an ECMAScript function that calls Reject(p, r).
var reject = function(r) { Reject(p, r); };
// 7. Call then.[[Call]](thenable, [resolve, reject]).
try {
then.call(thenable, resolve, reject);
} catch(x) {
threw = true;
e = x;
}
// 8. If calling the function throws an exception e,
if (threw) {
// call Reject(p, e).
Reject(p, e);
}
}
function CreatePromise() {
return new Promise(NO_RESOLVER);
}
module.exports = Promise;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment