Created
July 11, 2018 06:46
-
-
Save devsnek/888b58ada271524cc53bd526c008f908 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
'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