|
|
@ -26,16 +26,14 @@ |
|
|
|
// job queues, within a certain queue strict FIFO is required. See ES5.1
|
|
|
|
// https://www.ecma-international.org/ecma-262/6.0/#sec-jobs-and-job-queues:
|
|
|
|
// "The PendingJob records from a single Job Queue are always initiated in
|
|
|
|
// FIFO order. This specification does not define the order in which multiple
|
|
|
|
// Job Queues are serviced."
|
|
|
|
var queueHead = null; |
|
|
|
var queueTail = null; |
|
|
|
// FIFO order. This specification does not define the order in which
|
|
|
|
// multiple Job Queues are serviced."
|
|
|
|
var queueHead = null, queueTail = null; |
|
|
|
function enqueueJob(job) { |
|
|
|
if (queueHead) { |
|
|
|
queueTail.next = job; |
|
|
|
queueTail = job; |
|
|
|
queueTail.next = job; queueTail = job; |
|
|
|
} else { |
|
|
|
queueHead = queueTail = job; |
|
|
|
queueHead = job; queueTail = job; |
|
|
|
} |
|
|
|
} |
|
|
|
function dequeueJob() { |
|
|
@ -60,11 +58,16 @@ |
|
|
|
}); |
|
|
|
} |
|
|
|
|
|
|
|
// Promise detection (plain or subclassed Promise), in spec has [[PromiseState]]
|
|
|
|
// internal slot which isn't affected by Proxy behaviors etc.
|
|
|
|
// Promise detection (plain or subclassed Promise), in spec has
|
|
|
|
// [[PromiseState]] internal slot which isn't affected by Proxy
|
|
|
|
// behaviors etc.
|
|
|
|
var symMarker = Symbol('promise'); |
|
|
|
function isPromise(p) { return p !== null && typeof p === 'object' && symMarker in p; } |
|
|
|
function requirePromise(p) { if (!isPromise(p)) { throw new TypeError('Promise required'); } } |
|
|
|
function isPromise(p) { |
|
|
|
return p !== null && typeof p === 'object' && symMarker in p; |
|
|
|
} |
|
|
|
function requirePromise(p) { |
|
|
|
if (!isPromise(p)) { throw new TypeError('Promise required'); } |
|
|
|
} |
|
|
|
|
|
|
|
// Raw fulfill/reject operations, assume resolution processing done.
|
|
|
|
function doFulfill(p, val) { |
|
|
@ -73,7 +76,12 @@ |
|
|
|
var reactions = p.fulfillReactions; |
|
|
|
delete p.fulfillReactions; delete p.rejectReactions; |
|
|
|
reactions.forEach(function (r) { |
|
|
|
enqueueJob({ handler: r.handler, resolve: r.resolve, reject: r.reject, value: val }); // only value is new
|
|
|
|
enqueueJob({ |
|
|
|
handler: r.handler, |
|
|
|
resolve: r.resolve, |
|
|
|
reject: r.reject, |
|
|
|
value: val |
|
|
|
}); // only value is new
|
|
|
|
}); |
|
|
|
} |
|
|
|
function doReject(p, val) { |
|
|
@ -82,7 +90,12 @@ |
|
|
|
var reactions = p.rejectReactions; |
|
|
|
delete p.fulfillReactions; delete p.rejectReactions; |
|
|
|
reactions.forEach(function (r) { |
|
|
|
enqueueJob({ handler: r.handler, resolve: r.resolve, reject: r.reject, value: val }); // only value is new
|
|
|
|
enqueueJob({ |
|
|
|
handler: r.handler, |
|
|
|
resolve: r.resolve, |
|
|
|
reject: r.reject, |
|
|
|
value: val |
|
|
|
}); // only value is new
|
|
|
|
}); |
|
|
|
} |
|
|
|
|
|
|
@ -94,30 +107,40 @@ |
|
|
|
// to forcibly resolve/fulfill/reject a Promise regardless of extant
|
|
|
|
// resolve/reject functions.
|
|
|
|
function getResolutionFunctions(p) { |
|
|
|
// In ES2015 the resolve/reject functions have a shared 'state' object
|
|
|
|
// with a [[AlreadyResolved]] slot. Here we use an in-scope variable.
|
|
|
|
var alreadyResolved = false; |
|
|
|
var reject = function (err) { |
|
|
|
if (reject.state.alreadyResolved) { return; } |
|
|
|
reject.state.alreadyResolved = true; // neutralize this resolve/reject pair
|
|
|
|
if (alreadyResolved) { return; } |
|
|
|
alreadyResolved = true; // neutralize resolve/reject
|
|
|
|
if (p.state !== void 0) { return; } |
|
|
|
doReject(p, err); |
|
|
|
}; |
|
|
|
var resolve = function (val) { |
|
|
|
if (resolve.state.alreadyResolved) { return; } |
|
|
|
resolve.state.alreadyResolved = true; // neutralize this resolve/reject pair
|
|
|
|
if (alreadyResolved) { return; } |
|
|
|
alreadyResolved = true; // neutralize resolve/reject
|
|
|
|
if (p.state !== void 0) { return; } |
|
|
|
if (val === p) { return doReject(p, new TypeError('self resolution')); } |
|
|
|
if (val === p) { |
|
|
|
return doReject(p, new TypeError('self resolution')); |
|
|
|
} |
|
|
|
try { |
|
|
|
var then = (val !== null && typeof val === 'object' && val.then); |
|
|
|
var then = (val !== null && typeof val === 'object' && |
|
|
|
val.then); |
|
|
|
if (typeof then === 'function') { |
|
|
|
var t = getResolutionFunctions(p); |
|
|
|
return enqueueJob({ thenable: val, then: then, resolve: t.resolve, reject: t.reject }); |
|
|
|
// old resolve/reject is neutralized, only the new pair is live
|
|
|
|
return enqueueJob({ |
|
|
|
thenable: val, |
|
|
|
then: then, |
|
|
|
resolve: t.resolve, |
|
|
|
reject: t.reject |
|
|
|
}); |
|
|
|
// old resolve/reject is neutralized, only new pair is live
|
|
|
|
} |
|
|
|
return doFulfill(p, val); |
|
|
|
} catch (e) { |
|
|
|
return doReject(p, e); |
|
|
|
} |
|
|
|
}; |
|
|
|
reject.state = resolve.state = {}; // shared state for [[AlreadyResolved]]
|
|
|
|
return { resolve: resolve, reject: reject }; |
|
|
|
} |
|
|
|
|
|
|
@ -152,11 +175,15 @@ |
|
|
|
|
|
|
|
// %Promise% constructor.
|
|
|
|
var cons = function Promise(executor) { |
|
|
|
if (!new.target) { throw new TypeError('Promise must be called as a constructor'); } |
|
|
|
if (typeof executor !== 'function') { throw new TypeError('executor must be callable'); } |
|
|
|
if (!new.target) { |
|
|
|
throw new TypeError('Promise must be called as a constructor'); |
|
|
|
} |
|
|
|
if (typeof executor !== 'function') { |
|
|
|
throw new TypeError('executor must be callable'); |
|
|
|
} |
|
|
|
var _this = this; |
|
|
|
this[symMarker] = true; |
|
|
|
def(this, 'state', void 0); // undefined (pending), true (fulfilled), false (rejected)
|
|
|
|
def(this, 'state', void 0); // undefined (pending), true/false
|
|
|
|
def(this, 'value', void 0); |
|
|
|
def(this, 'fulfillReactions', []); |
|
|
|
def(this, 'rejectReactions', []); |
|
|
@ -168,7 +195,7 @@ |
|
|
|
} |
|
|
|
}; |
|
|
|
var proto = cons.prototype; |
|
|
|
Object.defineProperty(cons, 'prototype', { writable: false, enumerable: false, configurable: false }); |
|
|
|
def(cons, 'prototype', proto, ''); |
|
|
|
|
|
|
|
// %Promise%.resolve().
|
|
|
|
// XXX: direct handling
|
|
|
@ -186,28 +213,31 @@ |
|
|
|
// %Promise%.all().
|
|
|
|
function all(list) { |
|
|
|
var resolveFn, rejectFn; |
|
|
|
var p = new Promise(function (resolve, reject) { resolveFn = resolve; rejectFn = reject; }); |
|
|
|
var state = { values: [], remaining: 1, resolve: resolveFn, reject: rejectFn }; // remaining intentionally 1, not 0
|
|
|
|
var p = new Promise(function (resolve, reject) { |
|
|
|
resolveFn = resolve; rejectFn = reject; |
|
|
|
}); |
|
|
|
var values = []; |
|
|
|
var index = 0; |
|
|
|
var remaining = 1; // remaining intentionally 1, not 0
|
|
|
|
list.forEach(function (x) { // XXX: no iterator support
|
|
|
|
var t = Promise.resolve(x); |
|
|
|
var f = function promiseAllElement(val) { |
|
|
|
var F = promiseAllElement; |
|
|
|
var S = F.state; |
|
|
|
if (F.alreadyCalled) { return; } |
|
|
|
F.alreadyCalled = true; |
|
|
|
S.values[F.index] = val; |
|
|
|
if (--S.remaining === 0) { |
|
|
|
S.resolve.call(void 0, S.values); |
|
|
|
values[F.index] = val; |
|
|
|
if (--remaining === 0) { |
|
|
|
resolveFn.call(void 0, values); |
|
|
|
} |
|
|
|
}; |
|
|
|
f.state = state; |
|
|
|
// In ES2015 the functions would reference a shared state object
|
|
|
|
// explicitly. Here the conceptual state is in scope.
|
|
|
|
f.index = index++; |
|
|
|
state.remaining++; |
|
|
|
remaining++; |
|
|
|
t.then(f, rejectFn); |
|
|
|
}); |
|
|
|
if (--state.remaining === 0) { |
|
|
|
resolveFn.call(void 0, state.values); |
|
|
|
if (--remaining === 0) { |
|
|
|
resolveFn.call(void 0, values); |
|
|
|
} |
|
|
|
return p; |
|
|
|
} |
|
|
@ -215,7 +245,9 @@ |
|
|
|
// %Promise%.race().
|
|
|
|
function race(list) { |
|
|
|
var resolveFn, rejectFn; |
|
|
|
var p = new Promise(function (resolve, reject) { resolveFn = resolve; rejectFn = reject; }); |
|
|
|
var p = new Promise(function (resolve, reject) { |
|
|
|
resolveFn = resolve; rejectFn = reject; |
|
|
|
}); |
|
|
|
list.forEach(function (x) { // XXX: no iterator support
|
|
|
|
var t = Promise.resolve(x); |
|
|
|
t.then(resolveFn, rejectFn); |
|
|
@ -228,16 +260,36 @@ |
|
|
|
// No subclassing support here now, no NewPromiseCapability() handling.
|
|
|
|
requirePromise(this); |
|
|
|
var resolveFn, rejectFn; |
|
|
|
var p = new Promise(function (resolve, reject) { resolveFn = resolve; rejectFn = reject; }); |
|
|
|
onFulfilled = (typeof onFulfilled === 'function' ? onFulfilled : 'Identity'); |
|
|
|
onRejected = (typeof onRejected === 'function' ? onRejected : 'Thrower'); |
|
|
|
var p = new Promise(function (resolve, reject) { |
|
|
|
resolveFn = resolve; rejectFn = reject; |
|
|
|
}); |
|
|
|
if (typeof onFulfilled !== 'function') { onFulfilled = 'Identity' } |
|
|
|
if (typeof onRejected !== 'function') { onRejected = 'Thrower' } |
|
|
|
if (this.state === void 0) { // pending
|
|
|
|
this.fulfillReactions.push({ handler: onFulfilled, resolve: resolveFn, reject: rejectFn }); |
|
|
|
this.rejectReactions.push({ handler: onRejected, resolve: resolveFn, reject: rejectFn }); |
|
|
|
this.fulfillReactions.push({ |
|
|
|
handler: onFulfilled, |
|
|
|
resolve: resolveFn, |
|
|
|
reject: rejectFn |
|
|
|
}); |
|
|
|
this.rejectReactions.push({ |
|
|
|
handler: onRejected, |
|
|
|
resolve: resolveFn, |
|
|
|
reject: rejectFn |
|
|
|
}); |
|
|
|
} else if (this.state) { // fulfilled
|
|
|
|
enqueueJob({ handler: onFulfilled, resolve: resolveFn, reject: rejectFn, value: this.value }); |
|
|
|
enqueueJob({ |
|
|
|
handler: onFulfilled, |
|
|
|
resolve: resolveFn, |
|
|
|
reject: rejectFn, |
|
|
|
value: this.value |
|
|
|
}); |
|
|
|
} else { // rejected
|
|
|
|
enqueueJob({ handler: onRejected, resolve: resolveFn, reject: rejectFn, value: this.value }); |
|
|
|
enqueueJob({ |
|
|
|
handler: onRejected, |
|
|
|
resolve: resolveFn, |
|
|
|
reject: rejectFn, |
|
|
|
value: this.value |
|
|
|
}); |
|
|
|
} |
|
|
|
return p; |
|
|
|
} |
|
|
@ -268,7 +320,7 @@ |
|
|
|
def(proto, 'catch', _catch); |
|
|
|
def(proto, Symbol.toStringTag, 'Promise', 'c'); |
|
|
|
|
|
|
|
// Not part of the actual Promise API, but used to drive the "job queue".
|
|
|
|
// Custom API to drive the "job queue".
|
|
|
|
def(cons, 'runQueue', function _runQueueUntilEmpty() { |
|
|
|
while (runQueueEntry()) {} |
|
|
|
}); |
|
|
|