Browse Source

Promise polyfill lint and other trivia fixes

pull/1863/head
Sami Vaarala 7 years ago
parent
commit
5b8d91f67c
  1. 142
      polyfills/promise.js

142
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()) {}
});

Loading…
Cancel
Save