ayepromise
Version:
A teeny-tiny promise library
179 lines (151 loc) • 5.13 kB
JavaScript
// UMD header
(function (root, factory) {
if (typeof define === 'function' && define.amd) {
define(factory);
} else if (typeof exports === 'object') {
module.exports = factory();
} else {
root.ayepromise = factory();
}
}(this, function () {
'use strict';
var ayepromise = {};
/* Wrap an arbitrary number of functions and allow only one of them to be
executed and only once */
var once = function () {
var wasCalled = false;
return function wrapper(wrappedFunction) {
return function () {
if (wasCalled) {
return;
}
wasCalled = true;
wrappedFunction.apply(null, arguments);
};
};
};
var getThenableIfExists = function (obj) {
// Make sure we only access the accessor once as required by the spec
var then = obj && obj.then;
if (typeof obj === "object" && typeof then === "function") {
// Bind function back to it's object (so lousy 'this' will work)
return function() { return then.apply(obj, arguments); };
}
};
var aThenHandler = function (onFulfilled, onRejected) {
var defer = ayepromise.defer();
var doHandlerCall = function (func, value) {
setTimeout(function () {
var returnValue;
try {
returnValue = func(value);
} catch (e) {
defer.reject(e);
return;
}
if (returnValue === defer.promise) {
defer.reject(new TypeError('Cannot resolve promise with itself'));
} else {
defer.resolve(returnValue);
}
}, 1);
};
var callFulfilled = function (value) {
if (onFulfilled && onFulfilled.call) {
doHandlerCall(onFulfilled, value);
} else {
defer.resolve(value);
}
};
var callRejected = function (value) {
if (onRejected && onRejected.call) {
doHandlerCall(onRejected, value);
} else {
defer.reject(value);
}
};
return {
promise: defer.promise,
handle: function (state, value) {
if (state === FULFILLED) {
callFulfilled(value);
} else {
callRejected(value);
}
}
};
};
// States
var PENDING = 0,
FULFILLED = 1,
REJECTED = 2;
ayepromise.defer = function () {
var state = PENDING,
outcome,
thenHandlers = [];
var doSettle = function (settledState, value) {
state = settledState;
// Persist for handlers registered after settling
outcome = value;
thenHandlers.forEach(function (then) {
then.handle(state, outcome);
});
// Discard all references to handlers to be garbage collected
thenHandlers = null;
};
var doFulfill = function (value) {
doSettle(FULFILLED, value);
};
var doReject = function (error) {
doSettle(REJECTED, error);
};
var registerThenHandler = function (onFulfilled, onRejected) {
var thenHandler = aThenHandler(onFulfilled, onRejected);
if (state === PENDING) {
thenHandlers.push(thenHandler);
} else {
thenHandler.handle(state, outcome);
}
// Allow chaining of calls: something().then(...).then(...)
return thenHandler.promise;
};
var safelyResolveThenable = function (thenable) {
// Either fulfill, reject or reject with error
var onceWrapper = once();
try {
thenable(
onceWrapper(transparentlyResolveThenablesAndSettle),
onceWrapper(doReject)
);
} catch (e) {
onceWrapper(doReject)(e);
}
};
var transparentlyResolveThenablesAndSettle = function (value) {
var thenable;
try {
thenable = getThenableIfExists(value);
} catch (e) {
doReject(e);
return;
}
if (thenable) {
safelyResolveThenable(thenable);
} else {
doFulfill(value);
}
};
var onceWrapper = once();
return {
resolve: onceWrapper(transparentlyResolveThenablesAndSettle),
reject: onceWrapper(doReject),
promise: {
then: registerThenHandler,
fail: function (onRejected) {
return registerThenHandler(null, onRejected);
}
}
};
};
return ayepromise;
}));