311 lines
8.2 KiB
JavaScript
311 lines
8.2 KiB
JavaScript
/* eslint-disable no-var */
|
|
/* eslint-disable no-plusplus */
|
|
/* eslint-disable prefer-arrow-callback */
|
|
/* eslint-disable prefer-template */
|
|
/* eslint-disable func-names */
|
|
/* eslint-disable object-shorthand */
|
|
|
|
(function (factory) {
|
|
// eslint-disable-next-line no-unused-expressions
|
|
window.Promise || (window.Promise = factory());
|
|
// eslint-disable-next-line padded-blocks
|
|
}(function () {
|
|
|
|
var STATUS = {
|
|
PENDING: 'pending',
|
|
SEALED: 'sealed',
|
|
FULFILLED: 'fulfilled',
|
|
REJECTED: 'rejected',
|
|
};
|
|
|
|
var asyncSettimer = typeof setImmediate !== 'undefined' ? setImmediate : setTimeout;
|
|
var asyncQueue = [];
|
|
var asyncTimer;
|
|
|
|
function NOP() { }
|
|
|
|
function isArray(value) {
|
|
return Array.isArray ? Array.isArray(value) : Object.prototype.toString.call(value) === '[object Array]';
|
|
}
|
|
|
|
function asyncFlush() {
|
|
var i = 0;
|
|
// run
|
|
for (; i < asyncQueue.length; i++) {
|
|
asyncQueue[i][0](asyncQueue[i][1]);
|
|
}
|
|
|
|
// reset
|
|
asyncQueue = [];
|
|
asyncTimer = false;
|
|
}
|
|
|
|
function asyncCall(callback, arg) {
|
|
asyncQueue.push([callback, arg]);
|
|
|
|
if (!asyncTimer) {
|
|
asyncTimer = true;
|
|
asyncSettimer(asyncFlush, 0);
|
|
}
|
|
}
|
|
|
|
function publish(promise) {
|
|
var i = 0;
|
|
var callbacks = promise._then;
|
|
promise._then = null;
|
|
|
|
for (; i < callbacks.length; i++) {
|
|
// eslint-disable-next-line no-use-before-define
|
|
invokeCallback(callbacks[i]);
|
|
}
|
|
}
|
|
|
|
function publishFulfillment(promise) {
|
|
promise._state = STATUS.FULFILLED;
|
|
publish(promise);
|
|
}
|
|
|
|
function publishRejection(promise) {
|
|
promise._state = STATUS.REJECTED;
|
|
publish(promise);
|
|
}
|
|
|
|
function fulfill(promise, value) {
|
|
if (promise._state === STATUS.PENDING) {
|
|
promise._state = STATUS.SEALED;
|
|
promise._data = value;
|
|
|
|
asyncCall(publishFulfillment, promise);
|
|
}
|
|
}
|
|
|
|
function resolve(promise, value) {
|
|
// eslint-disable-next-line no-use-before-define
|
|
if (promise === value || !handleThenable(promise, value)) {
|
|
fulfill(promise, value);
|
|
}
|
|
}
|
|
|
|
function reject(promise, reason) {
|
|
if (promise._state === STATUS.PENDING) {
|
|
promise._state = STATUS.SEALED;
|
|
promise._data = reason;
|
|
|
|
asyncCall(publishRejection, promise);
|
|
}
|
|
}
|
|
|
|
function handleThenable(promise, value) {
|
|
var resolved;
|
|
|
|
try {
|
|
if (promise === value) {
|
|
throw new TypeError('A promise\'s callback cannot return that same promise');
|
|
}
|
|
|
|
if (value && (typeof value === 'function' || typeof value === 'object')) {
|
|
// eslint-disable-next-line vars-on-top
|
|
var then = value.then;
|
|
|
|
if (typeof then === 'function') {
|
|
then.call(value, function (val) {
|
|
if (!resolved) {
|
|
resolved = true;
|
|
|
|
if (value !== val) {
|
|
resolve(promise, val);
|
|
} else {
|
|
fulfill(promise, val);
|
|
}
|
|
}
|
|
}, function (reason) {
|
|
if (!resolved) {
|
|
resolved = true;
|
|
|
|
reject(promise, reason);
|
|
}
|
|
});
|
|
|
|
return true;
|
|
}
|
|
}
|
|
} catch (e) {
|
|
if (!resolved) {
|
|
reject(promise, e);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
function invokeResolver(fn, promise) {
|
|
function resolvePromise(value) {
|
|
resolve(promise, value);
|
|
}
|
|
|
|
function rejectPromise(reason) {
|
|
reject(promise, reason);
|
|
}
|
|
|
|
try {
|
|
fn(resolvePromise, rejectPromise);
|
|
} catch (e) {
|
|
rejectPromise(e);
|
|
}
|
|
}
|
|
|
|
function invokeCallback(subscriber) {
|
|
var owner = subscriber.owner;
|
|
var settled = owner._state;
|
|
var value = owner._data;
|
|
var callback = subscriber[settled];
|
|
var promise = subscriber.then;
|
|
|
|
if (typeof callback === 'function') {
|
|
settled = STATUS.FULFILLED;
|
|
try {
|
|
value = callback(value);
|
|
} catch (e) {
|
|
reject(promise, e);
|
|
}
|
|
}
|
|
|
|
if (!handleThenable(promise, value)) {
|
|
if (settled === STATUS.FULFILLED) {
|
|
resolve(promise, value);
|
|
} else if (settled === STATUS.REJECTED) {
|
|
reject(promise, value);
|
|
}
|
|
}
|
|
}
|
|
|
|
function Promise(fn) {
|
|
if (!(this instanceof Promise)) {
|
|
throw new TypeError(this + ' is not a promise');
|
|
}
|
|
if (typeof fn !== 'function') {
|
|
throw new TypeError('Promise resolver ' + fn + ' is not a function');
|
|
}
|
|
|
|
this._then = [];
|
|
|
|
invokeResolver(fn, this);
|
|
}
|
|
|
|
Promise.prototype = {
|
|
_state: STATUS.PENDING,
|
|
_then: null,
|
|
_data: undefined,
|
|
|
|
then: function (onResolver, onRejection) {
|
|
var subscriber = {
|
|
owner: this,
|
|
then: new Promise(NOP),
|
|
fulfilled: onResolver,
|
|
rejected: onRejection,
|
|
};
|
|
|
|
if (this._state === STATUS.FULFILLED || this._state === STATUS.REJECTED) {
|
|
// already resolved
|
|
asyncCall(invokeCallback, subscriber);
|
|
} else {
|
|
// subscribe
|
|
this._then.push(subscriber);
|
|
}
|
|
return subscriber.then;
|
|
},
|
|
// eslint-disable-next-line quote-props
|
|
'catch': function (onRejection) {
|
|
return this.then(null, onRejection);
|
|
},
|
|
};
|
|
|
|
// eslint-disable-next-line no-extend-native
|
|
Object.defineProperty(Promise.prototype, 'constructor', { value: Promise });
|
|
|
|
Promise.all = function (promises) {
|
|
var This = this;
|
|
|
|
if (!isArray(promises)) {
|
|
throw new TypeError(promises + ' is not an array');
|
|
}
|
|
|
|
return new This(function (onresolve, onreject) {
|
|
var results = [];
|
|
var remaining = 0;
|
|
var i = 0;
|
|
var promise;
|
|
|
|
function resolver(index) {
|
|
remaining++;
|
|
return function (value) {
|
|
results[index] = value;
|
|
if (!--remaining) {
|
|
onresolve(results);
|
|
}
|
|
};
|
|
}
|
|
|
|
for (; i < promises.length; i++) {
|
|
promise = promises[i];
|
|
|
|
if (promise && typeof promise.then === 'function') {
|
|
promise.then(resolver(i), onreject);
|
|
} else {
|
|
results[i] = promise;
|
|
}
|
|
}
|
|
|
|
if (!remaining) {
|
|
onresolve(results);
|
|
}
|
|
});
|
|
};
|
|
|
|
Promise.race = function (promises) {
|
|
var This = this;
|
|
|
|
if (!isArray(promises)) {
|
|
throw new TypeError(promises + ' is not an array');
|
|
}
|
|
|
|
return new This(function (onresolve, onreject) {
|
|
var i = 0;
|
|
var promise;
|
|
for (; i < promises.length; i++) {
|
|
promise = promises[i];
|
|
|
|
if (promise && typeof promise.then === 'function') {
|
|
promise.then(onresolve, onreject);
|
|
} else {
|
|
onresolve(promise);
|
|
}
|
|
}
|
|
});
|
|
};
|
|
|
|
Promise.resolve = function (value) {
|
|
var This = this;
|
|
|
|
if (value && typeof value === 'object' && value.constructor === This) {
|
|
return value;
|
|
}
|
|
|
|
return new This(function (onresolve) {
|
|
onresolve(value);
|
|
});
|
|
};
|
|
|
|
Promise.reject = function (reason) {
|
|
var This = this;
|
|
|
|
return new This(function (onresolve, onreject) {
|
|
onreject(reason);
|
|
});
|
|
};
|
|
|
|
return Promise;
|
|
}));
|