(function () { "use strict"; /*global setImmediate, MessageChannel*/ function definePromise(declare, extended, array, is, fn, args) { var forEach = array.forEach, isUndefinedOrNull = is.isUndefinedOrNull, isArray = is.isArray, isFunction = is.isFunction, isBoolean = is.isBoolean, bind = fn.bind, bindIgnore = fn.bindIgnore, argsToArray = args.argsToArray; function createHandler(fn, promise) { return function _handler() { try { when(fn.apply(null, arguments)) .addCallback(promise) .addErrback(promise); } catch (e) { promise.errback(e); } }; } var nextTick; if (typeof setImmediate === "function") { // In IE10, or use https://github.com/NobleJS/setImmediate if (typeof window !== "undefined") { nextTick = setImmediate.bind(window); } else { nextTick = setImmediate; } } else if (typeof process !== "undefined") { // node nextTick = function (cb) { process.nextTick(cb); }; } else if (typeof MessageChannel !== "undefined") { // modern browsers // http://www.nonblocking.io/2011/06/windownexttick.html var channel = new MessageChannel(); // linked list of tasks (single, with head node) var head = {}, tail = head; channel.port1.onmessage = function () { head = head.next; var task = head.task; delete head.task; task(); }; nextTick = function (task) { tail = tail.next = {task: task}; channel.port2.postMessage(0); }; } else { // old browsers nextTick = function (task) { setTimeout(task, 0); }; } //noinspection JSHint var Promise = declare({ instance: { __fired: false, __results: null, __error: null, __errorCbs: null, __cbs: null, constructor: function () { this.__errorCbs = []; this.__cbs = []; fn.bindAll(this, ["callback", "errback", "resolve", "classic", "__resolve", "addCallback", "addErrback"]); }, __resolve: function () { if (!this.__fired) { this.__fired = true; var cbs = this.__error ? this.__errorCbs : this.__cbs, len = cbs.length, i, results = this.__error || this.__results; for (i = 0; i < len; i++) { this.__callNextTick(cbs[i], results); } } }, __callNextTick: function (cb, results) { nextTick(function () { cb.apply(this, results); }); }, addCallback: function (cb) { if (cb) { if (isPromiseLike(cb) && cb.callback) { cb = cb.callback; } if (this.__fired && this.__results) { this.__callNextTick(cb, this.__results); } else { this.__cbs.push(cb); } } return this; }, addErrback: function (cb) { if (cb) { if (isPromiseLike(cb) && cb.errback) { cb = cb.errback; } if (this.__fired && this.__error) { this.__callNextTick(cb, this.__error); } else { this.__errorCbs.push(cb); } } return this; }, callback: function (args) { if (!this.__fired) { this.__results = arguments; this.__resolve(); } return this.promise(); }, errback: function (args) { if (!this.__fired) { this.__error = arguments; this.__resolve(); } return this.promise(); }, resolve: function (err, args) { if (err) { this.errback(err); } else { this.callback.apply(this, argsToArray(arguments, 1)); } return this; }, classic: function (cb) { if ("function" === typeof cb) { this.addErrback(function (err) { cb(err); }); this.addCallback(function () { cb.apply(this, [null].concat(argsToArray(arguments))); }); } return this; }, then: function (callback, errback) { var promise = new Promise(), errorHandler = promise; if (isFunction(errback)) { errorHandler = createHandler(errback, promise); } this.addErrback(errorHandler); if (isFunction(callback)) { this.addCallback(createHandler(callback, promise)); } else { this.addCallback(promise); } return promise.promise(); }, both: function (callback) { return this.then(callback, callback); }, promise: function () { var ret = { then: bind(this, "then"), both: bind(this, "both"), promise: function () { return ret; } }; forEach(["addCallback", "addErrback", "classic"], function (action) { ret[action] = bind(this, function () { this[action].apply(this, arguments); return ret; }); }, this); return ret; } } }); var PromiseList = Promise.extend({ instance: { /*@private*/ __results: null, /*@private*/ __errors: null, /*@private*/ __promiseLength: 0, /*@private*/ __defLength: 0, /*@private*/ __firedLength: 0, normalizeResults: false, constructor: function (defs, normalizeResults) { this.__errors = []; this.__results = []; this.normalizeResults = isBoolean(normalizeResults) ? normalizeResults : false; this._super(arguments); if (defs && defs.length) { this.__defLength = defs.length; forEach(defs, this.__addPromise, this); } else { this.__resolve(); } }, __addPromise: function (promise, i) { promise.then( bind(this, function () { var args = argsToArray(arguments); args.unshift(i); this.callback.apply(this, args); }), bind(this, function () { var args = argsToArray(arguments); args.unshift(i); this.errback.apply(this, args); }) ); }, __resolve: function () { if (!this.__fired) { this.__fired = true; var cbs = this.__errors.length ? this.__errorCbs : this.__cbs, len = cbs.length, i, results = this.__errors.length ? this.__errors : this.__results; for (i = 0; i < len; i++) { this.__callNextTick(cbs[i], results); } } }, __callNextTick: function (cb, results) { nextTick(function () { cb.apply(null, [results]); }); }, addCallback: function (cb) { if (cb) { if (isPromiseLike(cb) && cb.callback) { cb = bind(cb, "callback"); } if (this.__fired && !this.__errors.length) { this.__callNextTick(cb, this.__results); } else { this.__cbs.push(cb); } } return this; }, addErrback: function (cb) { if (cb) { if (isPromiseLike(cb) && cb.errback) { cb = bind(cb, "errback"); } if (this.__fired && this.__errors.length) { this.__callNextTick(cb, this.__errors); } else { this.__errorCbs.push(cb); } } return this; }, callback: function (i) { if (this.__fired) { throw new Error("Already fired!"); } var args = argsToArray(arguments); if (this.normalizeResults) { args = args.slice(1); args = args.length === 1 ? args.pop() : args; } this.__results[i] = args; this.__firedLength++; if (this.__firedLength === this.__defLength) { this.__resolve(); } return this.promise(); }, errback: function (i) { if (this.__fired) { throw new Error("Already fired!"); } var args = argsToArray(arguments); if (this.normalizeResults) { args = args.slice(1); args = args.length === 1 ? args.pop() : args; } this.__errors[i] = args; this.__firedLength++; if (this.__firedLength === this.__defLength) { this.__resolve(); } return this.promise(); } } }); function callNext(list, results, propogate) { var ret = new Promise().callback(); forEach(list, function (listItem) { ret = ret.then(propogate ? listItem : bindIgnore(null, listItem)); if (!propogate) { ret = ret.then(function (res) { results.push(res); return results; }); } }); return ret; } function isPromiseLike(obj) { return !isUndefinedOrNull(obj) && (isFunction(obj.then)); } function wrapThenPromise(p) { var ret = new Promise(); p.then(bind(ret, "callback"), bind(ret, "errback")); return ret.promise(); } function when(args) { var p; args = argsToArray(arguments); if (!args.length) { p = new Promise().callback(args).promise(); } else if (args.length === 1) { args = args.pop(); if (isPromiseLike(args)) { if (args.addCallback && args.addErrback) { p = new Promise(); args.addCallback(p.callback); args.addErrback(p.errback); } else { p = wrapThenPromise(args); } } else if (isArray(args) && array.every(args, isPromiseLike)) { p = new PromiseList(args, true).promise(); } else { p = new Promise().callback(args); } } else { p = new PromiseList(array.map(args, function (a) { return when(a); }), true).promise(); } return p; } function wrap(fn, scope) { return function _wrap() { var ret = new Promise(); var args = argsToArray(arguments); args.push(ret.resolve); fn.apply(scope || this, args); return ret.promise(); }; } function serial(list) { if (isArray(list)) { return callNext(list, [], false); } else { throw new Error("When calling promise.serial the first argument must be an array"); } } function chain(list) { if (isArray(list)) { return callNext(list, [], true); } else { throw new Error("When calling promise.serial the first argument must be an array"); } } function wait(args, fn) { args = argsToArray(arguments); var resolved = false; fn = args.pop(); var p = when(args); return function waiter() { if (!resolved) { args = arguments; return p.then(bind(this, function doneWaiting() { resolved = true; return fn.apply(this, args); })); } else { return when(fn.apply(this, arguments)); } }; } function createPromise() { return new Promise(); } function createPromiseList(promises) { return new PromiseList(promises, true).promise(); } function createRejected(val) { return createPromise().errback(val); } function createResolved(val) { return createPromise().callback(val); } return extended .define({ isPromiseLike: isPromiseLike }).expose({ isPromiseLike: isPromiseLike, when: when, wrap: wrap, wait: wait, serial: serial, chain: chain, Promise: Promise, PromiseList: PromiseList, promise: createPromise, defer: createPromise, deferredList: createPromiseList, reject: createRejected, resolve: createResolved }); } if ("undefined" !== typeof exports) { if ("undefined" !== typeof module && module.exports) { module.exports = definePromise(require("declare.js"), require("extended"), require("array-extended"), require("is-extended"), require("function-extended"), require("arguments-extended")); } } else if ("function" === typeof define && define.amd) { define(["declare", "extended", "array-extended", "is-extended", "function-extended", "arguments-extended"], function (declare, extended, array, is, fn, args) { return definePromise(declare, extended, array, is, fn, args); }); } else { this.promiseExtended = definePromise(this.declare, this.extended, this.arrayExtended, this.isExtended, this.functionExtended, this.argumentsExtended); } }).call(this);