Skip to content

Instantly share code, notes, and snippets.

@devsnek
Created July 11, 2018 06:46
Show Gist options
  • Save devsnek/888b58ada271524cc53bd526c008f908 to your computer and use it in GitHub Desktop.
Save devsnek/888b58ada271524cc53bd526c008f908 to your computer and use it in GitHub Desktop.
'use strict';
const kPromiseState = Symbol('[[PromiseState]]');
const kPromiseFulfillReactions = Symbol('[[PromiseFulfillReactions]]');
const kPromiseRejectReactions = Symbol('[[PromiseRejectReactions]]');
const kPromiseIsHandled = Symbol('[[PromiseIsHandled]]');
const kPromiseResult = Symbol('[[PromiseResult]]');
const kPromise = Symbol('[[Promise]]');
const kResolve = Symbol('[[Resolve]]');
const kReject = Symbol('[[Reject]]');
const kCapability = Symbol('[[Capability]]');
const kType = Symbol('[[Type]]');
const kHandler = Symbol('[[Handler]]');
function HostPromiseRejectionTracker(promise, operation) { // eslint-disable-line no-unused-vars
}
function EnqueueJob(queueName, job, args) {
setTimeout(job, 1, ...args);
}
function PromiseReactionJob(reaction, argument) {
const promiseCapability = reaction[kCapability];
const type = reaction[kType];
const handler = reaction[kHandler];
let handlerResult;
let abruptCompletion = false;
if (handler === undefined) {
if (type === 'Fulfill') {
handlerResult = argument;
} else {
handlerResult = argument;
abruptCompletion = true;
}
} else {
try {
handlerResult = handler(argument);
} catch (e) {
handlerResult = e;
abruptCompletion = true;
}
}
let status;
if (abruptCompletion) {
status = promiseCapability[kReject](handlerResult);
} else {
status = promiseCapability[kResolve](handlerResult);
}
return status;
}
function PromiseResolveThenableJob(promiseToResolve, thenable, then) {
// eslint-disable-next-line no-use-before-define
const resolvingFunctions = CreateResolvingFunctions(promiseToResolve);
try {
const thenCallResult = then.call(
thenable, resolvingFunctions[kResolve], resolvingFunctions[kReject],
);
return thenCallResult;
} catch (thenCallResult) {
const status = resolvingFunctions[kReject](thenCallResult);
return status;
}
}
function NewPromiseCapability(C) {
const promiseCapability = {
[kPromise]: undefined,
[kResolve]: undefined,
[kReject]: undefined,
};
const promise = new C((resolve, reject) => {
// GetCapabilitiesExecutor
promiseCapability[kResolve] = resolve;
promiseCapability[kReject] = reject;
});
promiseCapability[kPromise] = promise;
return promiseCapability;
}
function TriggerPromiseReactions(reactions, argument) {
reactions.forEach((reaction) => {
EnqueueJob('PromiseJobs', PromiseReactionJob, [reaction, argument]);
});
return undefined;
}
function FulfillPromise(promise, value) {
const reactions = promise[kPromiseFulfillReactions];
promise[kPromiseResult] = value;
promise[kPromiseFulfillReactions] = undefined;
promise[kPromiseRejectReactions] = undefined;
promise[kPromiseState] = 'fulfilled';
return TriggerPromiseReactions(reactions, value);
}
function RejectPromise(promise, reason) {
const reactions = promise[kPromiseRejectReactions];
promise[kPromiseResult] = reason;
promise[kPromiseFulfillReactions] = undefined;
promise[kPromiseRejectReactions] = undefined;
promise[kPromiseState] = 'rejected';
HostPromiseRejectionTracker(promise, 'reject');
return TriggerPromiseReactions(reactions, reason);
}
function PromiseResolve(C, x) {
const xConstructor = x.constructor;
if (C === xConstructor) {
return x;
}
const promiseCapability = NewPromiseCapability(C);
promiseCapability[kResolve](x);
return promiseCapability[kPromise];
}
function PerformPromiseThen(promise, onFulfilled, onRejected, resultCapability) {
if (typeof onFulfilled !== 'function') {
onFulfilled = undefined;
}
if (typeof onRejected !== 'function') {
onRejected = undefined;
}
const fulfillReaction = {
[kCapability]: resultCapability,
[kType]: 'Fulfill',
[kHandler]: onFulfilled,
};
const rejectReaction = {
[kCapability]: resultCapability,
[kType]: 'Reject',
[kHandler]: onRejected,
};
if (promise[kPromiseState] === 'pending') {
promise[kPromiseFulfillReactions].push(fulfillReaction);
promise[kPromiseRejectReactions].push(rejectReaction);
} else if (promise[kPromiseState] === 'fulfilled') {
const value = promise[kPromiseResult];
EnqueueJob('PromiseJobs', PromiseReactionJob, [fulfillReaction, value]);
} else {
const reason = promise[kPromiseResult];
HostPromiseRejectionTracker(promise, 'handle');
EnqueueJob('PromiseJobs', PromiseReactionJob, [rejectReaction, reason]);
}
promise[kPromiseIsHandled] = true;
return resultCapability[kPromise];
}
function CreateResolvingFunctions(promise) {
let alreadyResolved = false;
const resolve = (resolution) => {
if (alreadyResolved) {
return undefined;
}
alreadyResolved = true;
if (resolution === promise) {
const selfResolutionError = new TypeError('cannot reject a promise with itself');
return RejectPromise(promise, selfResolutionError);
}
if (typeof resolution !== 'object' || resolution === null) {
return FulfillPromise(promise, resolution);
}
const then = resolution.then; // eslint-disable-line prefer-destructuring
if (typeof then !== 'function') {
return FulfillPromise(promise, resolution);
}
const thenAction = then;
EnqueueJob('PromiseJobs', PromiseResolveThenableJob, [promise, resolution, thenAction]);
return undefined;
};
const reject = (reason) => {
if (alreadyResolved === true) {
return undefined;
}
alreadyResolved = true;
return RejectPromise(promise, reason);
};
return {
[kResolve]: resolve,
[kReject]: reject,
};
}
function PerformPromiseAll(iteratorRecord, constructor, resultCapability) {
const values = [];
let remainingElementsCount = 1;
let index = 0;
while (true) { // eslint-disable-line no-constant-condition
const { value: nextValue, done } = iteratorRecord.next();
if (done) {
remainingElementsCount -= 1;
if (remainingElementsCount === 0) {
resultCapability[kResolve](values);
}
return resultCapability[kPromise];
}
values.push(undefined);
const nextPromise = constructor.resolve(nextValue);
let alreadyCalled = false;
const savedIndex = index;
const resolveElement = (x) => { // eslint-disable-line no-loop-func
if (alreadyCalled) {
return undefined;
}
alreadyCalled = true;
values[savedIndex] = x;
remainingElementsCount -= 1;
if (remainingElementsCount === 0) {
return resultCapability[kResolve](values);
}
return undefined;
};
remainingElementsCount += 1;
nextPromise.then(resolveElement, resultCapability[kReject]);
index += 1;
}
}
function PerformPromiseRace(iteratorRecord, constructor, resultCapability) {
while (true) { // eslint-disable-line no-constant-condition
const { value: nextValue, done } = iteratorRecord.next();
if (done) {
return resultCapability[kPromise];
}
const nextPromise = constructor.resolve(nextValue);
nextPromise.then(resultCapability[kResolve], resultCapability[kReject]);
}
}
class Promise {
static all(iterable) {
const C = this;
const promiseCapability = NewPromiseCapability(C);
try {
const iteratorRecord = iterable[Symbol.iterator]();
const result = PerformPromiseAll(iteratorRecord, C, promiseCapability);
return result;
} catch (abruptCompletion) {
return promiseCapability[kReject](abruptCompletion);
}
}
static race(iterable) {
const C = this;
const promiseCapability = NewPromiseCapability(C);
try {
const iteratorRecord = iterable[Symbol.iterator]();
const result = PerformPromiseRace(iteratorRecord, C, promiseCapability);
return result;
} catch (abruptCompletion) {
return promiseCapability[kReject](abruptCompletion);
}
}
static resolve(x) {
const C = this;
return PromiseResolve(C, x);
}
static reject(r) {
const C = this;
const promiseCapability = NewPromiseCapability(C);
promiseCapability[kReject](r);
return promiseCapability[kPromise];
}
constructor(fn) {
if (typeof fn !== 'function') {
throw new TypeError(`${fn} is not a function`);
}
this[kPromiseState] = 'pending';
this[kPromiseFulfillReactions] = [];
this[kPromiseRejectReactions] = [];
this[kPromiseIsHandled] = false;
const resolvingFunctions = CreateResolvingFunctions(this);
try {
fn(resolvingFunctions[kResolve], resolvingFunctions[kReject]);
} catch (err) {
resolvingFunctions[kReject](err);
}
}
then(onFulfilled, onRejected) {
const promise = this;
const C = promise.constructor;
const resultCapability = NewPromiseCapability(C);
return PerformPromiseThen(promise, onFulfilled, onRejected, resultCapability);
}
catch(onRejected) {
const promise = this;
return promise.then(undefined, onRejected);
}
finally(onFinally) {
const promise = this;
const C = promise.constructor;
let thenFinally;
let catchFinally;
if (typeof onFinally !== 'function') {
thenFinally = onFinally;
catchFinally = onFinally;
} else {
thenFinally = (length) => { // eslint-disable-line no-unused-vars
const result = onFinally();
const p = PromiseResolve(C, result);
const valueThunk = (value) => value;
return p.then(valueThunk);
};
catchFinally = (length) => { // eslint-disable-line no-unused-vars
const result = onFinally();
const p = PromiseResolve(C, result);
const thrower = (value) => {
throw value;
};
return p.then(thrower);
};
}
return promise.then(thenFinally, catchFinally);
}
}
module.exports = Promise;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment