diff --git a/polyfills/promise.js b/polyfills/promise.js index 330dc4a3..f42c9d36 100644 --- a/polyfills/promise.js +++ b/polyfills/promise.js @@ -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()) {} });