(function(exports) { "use strict"; var config = {}; var browserGlobal = (typeof window !== 'undefined') ? window : {}; var MutationObserver = browserGlobal.MutationObserver || browserGlobal.WebKitMutationObserver; var RSVP; if (typeof process !== 'undefined' && {}.toString.call(process) === '[object process]') { config.async = function(callback, binding) { process.nextTick(function() { callback.call(binding); }); }; } else if (MutationObserver) { var queue = []; var observer = new MutationObserver(function() { var toProcess = queue.slice(); queue = []; toProcess.forEach(function(tuple) { var callback = tuple[0], binding = tuple[1]; callback.call(binding); }); }); var element = document.createElement('div'); observer.observe(element, { attributes: true }); // Chrome Memory Leak: https://bugs.webkit.org/show_bug.cgi?id=93661 window.addEventListener('unload', function(){ observer.disconnect(); observer = null; }); config.async = function(callback, binding) { queue.push([callback, binding]); element.setAttribute('drainQueue', 'drainQueue'); }; } else { config.async = function(callback, binding) { setTimeout(function() { callback.call(binding); }, 1); }; } var Event = function(type, options) { this.type = type; for (var option in options) { if (!options.hasOwnProperty(option)) { continue; } this[option] = options[option]; } }; var indexOf = function(callbacks, callback) { for (var i=0, l=callbacks.length; i<l; i++) { if (callbacks[i][0] === callback) { return i; } } return -1; }; var callbacksFor = function(object) { var callbacks = object._promiseCallbacks; if (!callbacks) { callbacks = object._promiseCallbacks = {}; } return callbacks; }; var EventTarget = { mixin: function(object) { object.on = this.on; object.off = this.off; object.trigger = this.trigger; return object; }, on: function(eventNames, callback, binding) { var allCallbacks = callbacksFor(this), callbacks, eventName; eventNames = eventNames.split(/\s+/); binding = binding || this; while (eventName = eventNames.shift()) { callbacks = allCallbacks[eventName]; if (!callbacks) { callbacks = allCallbacks[eventName] = []; } if (indexOf(callbacks, callback) === -1) { callbacks.push([callback, binding]); } } }, off: function(eventNames, callback) { var allCallbacks = callbacksFor(this), callbacks, eventName, index; eventNames = eventNames.split(/\s+/); while (eventName = eventNames.shift()) { if (!callback) { allCallbacks[eventName] = []; continue; } callbacks = allCallbacks[eventName]; index = indexOf(callbacks, callback); if (index !== -1) { callbacks.splice(index, 1); } } }, trigger: function(eventName, options) { var allCallbacks = callbacksFor(this), callbacks, callbackTuple, callback, binding, event; if (callbacks = allCallbacks[eventName]) { for (var i=0, l=callbacks.length; i<l; i++) { callbackTuple = callbacks[i]; callback = callbackTuple[0]; binding = callbackTuple[1]; if (typeof options !== 'object') { options = { detail: options }; } event = new Event(eventName, options); callback.call(binding, event); } } } }; var Promise = function() { this.on('promise:resolved', function(event) { this.trigger('success', { detail: event.detail }); }, this); this.on('promise:failed', function(event) { this.trigger('error', { detail: event.detail }); }, this); }; var noop = function() {}; var invokeCallback = function(type, promise, callback, event) { var hasCallback = typeof callback === 'function', value, error, succeeded, failed; if (hasCallback) { try { value = callback(event.detail); succeeded = true; } catch(e) { failed = true; error = e; } } else { value = event.detail; succeeded = true; } if (value && typeof value.then === 'function') { value.then(function(value) { promise.resolve(value); }, function(error) { promise.reject(error); }); } else if (hasCallback && succeeded) { promise.resolve(value); } else if (failed) { promise.reject(error); } else { promise[type](value); } }; Promise.prototype = { then: function(done, fail) { var thenPromise = new Promise(); if (this.isResolved) { config.async(function() { invokeCallback('resolve', thenPromise, done, { detail: this.resolvedValue }); }, this); } if (this.isRejected) { config.async(function() { invokeCallback('reject', thenPromise, fail, { detail: this.rejectedValue }); }, this); } this.on('promise:resolved', function(event) { invokeCallback('resolve', thenPromise, done, event); }); this.on('promise:failed', function(event) { invokeCallback('reject', thenPromise, fail, event); }); return thenPromise; }, resolve: function(value) { resolve(this, value); this.resolve = noop; this.reject = noop; }, reject: function(value) { reject(this, value); this.resolve = noop; this.reject = noop; } }; function resolve(promise, value) { config.async(function() { promise.trigger('promise:resolved', { detail: value }); promise.isResolved = true; promise.resolvedValue = value; }); } function reject(promise, value) { config.async(function() { promise.trigger('promise:failed', { detail: value }); promise.isRejected = true; promise.rejectedValue = value; }); } function all(promises) { var i, results = []; var allPromise = new Promise(); var remaining = promises.length; if (remaining === 0) { allPromise.resolve([]); } var resolver = function(index) { return function(value) { resolve(index, value); }; }; var resolve = function(index, value) { results[index] = value; if (--remaining === 0) { allPromise.resolve(results); } }; var reject = function(error) { allPromise.reject(error); }; for (i = 0; i < remaining; i++) { promises[i].then(resolver(i), reject); } return allPromise; } EventTarget.mixin(Promise.prototype); function configure(name, value) { config[name] = value; } exports.Promise = Promise; exports.Event = Event; exports.EventTarget = EventTarget; exports.all = all; exports.configure = configure; })(window.RSVP = {});