Created
August 31, 2013 15:55
-
-
Save anonymous/6399102 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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; | |
// 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: | |
defer(function() { | |
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