|
/** |
|
* @see http://stackoverflow.com/questions/23772801/basic-javascript-promise-implementation-attempt/23785244#23785244 |
|
* @see https://www.promisejs.org/implementing/ |
|
*/ |
|
const Pending = 0; |
|
const Fulfilled = 1; |
|
const Rejected = 2; |
|
|
|
function Promise(unsafeResolver) { |
|
let state = Pending; |
|
let value = null; |
|
let thens = []; // opt |
|
doResolve(unsafeResolver, resolve, reject); |
|
|
|
// transition |
|
function fulfill(result) { |
|
state = Fulfilled; |
|
value = result; |
|
thens.forEach(handleThen); // opt |
|
thens = null; |
|
} |
|
|
|
function reject(result) { |
|
state = Rejected; |
|
value = result; |
|
thens.forEach(handleThen); // opt |
|
thens = null; |
|
} |
|
|
|
// callback to resolve our promise. Trigger by some external script |
|
function resolve(result) { |
|
try { |
|
const then = getThen(result); |
|
then |
|
? doResolve(then.bind(result), resolve, reject) |
|
: fulfill(result); |
|
} catch (e) { |
|
reject(e); |
|
} |
|
} |
|
|
|
/** |
|
* Resolve the parent promise with the value of child promise |
|
* |
|
* @param unsafeResolver user provider resolver function, could potentially try to resolve more than once |
|
* @param onFulfilled to resolve parent promise |
|
* @param onRejected to reject parent promise |
|
*/ |
|
function doResolve(unsafeResolver, onFulfilled, onRejected) { |
|
let done = false; |
|
|
|
const onFulfilledOnce = value => { |
|
if (done) return; |
|
done = true; |
|
onFulfilled(value); |
|
}; |
|
|
|
const onRejectedOnce = value => { |
|
if (done) return; |
|
done = true; |
|
onRejected(value); |
|
}; |
|
|
|
try { |
|
unsafeResolver(onFulfilledOnce, onRejectedOnce); |
|
} catch (e) { |
|
onRejectedOnce(e); // opt: inline |
|
} |
|
} |
|
|
|
function handleThen(thenHandler) { // opt: don't create object |
|
// Resolve callback (created via `.done` that has been waiting for this promise |
|
// to resolve |
|
if (state === Pending) { |
|
thens.push(thenHandler); |
|
} else if (state === Fulfilled && typeof thenHandler.onFulfilled === 'function') { |
|
thenHandler.onFulfilled(value); |
|
} else if (state === Rejected && typeof thenHandler.onRejected === 'function') { |
|
thenHandler.onRejected(value); |
|
} |
|
} |
|
|
|
// Observing via done, `onFulfilled` and `onRejected` must be invoked after |
|
// `done` is finished |
|
function done(onFulfilled, onRejected) { |
|
setTimeout(() => handleThen({ onFulfilled, onRejected }), 0); |
|
} |
|
|
|
this.then = (onFulfilled, onRejected) => { |
|
const self = this; |
|
return new Promise((resolve, reject) => |
|
self.done( |
|
value => { |
|
if (typeof onFulfilled === 'function') { |
|
try { |
|
resolve(onFulfilled(value)); |
|
} catch (e) { |
|
reject(e); |
|
} |
|
} else { |
|
resolve(value); |
|
} |
|
}, |
|
value => { |
|
if (typeof onRejected === 'function') { |
|
try { |
|
resolve(onRejected(value)); |
|
} catch (e) { |
|
reject(e); |
|
} |
|
} else { |
|
reject(value); |
|
} |
|
} |
|
) |
|
); |
|
}; |
|
} |
|
|
|
function getThen(promise) { |
|
if (promise && (typeof promise === 'function' || typeof promise === 'object')) { |
|
const then = promise.then; |
|
if (typeof then === 'function') |
|
return then; |
|
} |
|
|
|
return null; |
|
} |