deferred.js 4.02 KB
Newer Older
David Luecke's avatar
David Luecke committed
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174
/*
* CanJS - 1.1.3 (2012-12-11)
* http://canjs.us/
* Copyright (c) 2012 Bitovi
* Licensed MIT
*/
define(['can/util/can'], function (can) {

	// deferred.js
	// ---------
	// _Lightweight, jQuery style deferreds._
	// extend is usually provided by the wrapper but to avoid steal.then calls
	// we define a simple extend here as well
	var extend = function (target, src) {
		for (var key in src) {
			if (src.hasOwnProperty(key)) {
				target[key] = src[key];
			}
		}
	},
		Deferred = function (func) {
			if (!(this instanceof Deferred)) return new Deferred();

			this._doneFuncs = [];
			this._failFuncs = [];
			this._resultArgs = null;
			this._status = "";

			// Check for option `function` -- call it with this as context and as first
			// parameter, as specified in jQuery API.
			func && func.call(this, this);
		};

	can.Deferred = Deferred;
	can.when = Deferred.when = function () {
		var args = can.makeArray(arguments);
		if (args.length < 2) {
			var obj = args[0];
			if (obj && (can.isFunction(obj.isResolved) && can.isFunction(obj.isRejected))) {
				return obj;
			} else {
				return Deferred().resolve(obj);
			}
		} else {

			var df = Deferred(),
				done = 0,
				// Resolve params -- params of each resolve, we need to track them down 
				// to be able to pass them in the correct order if the master 
				// needs to be resolved.
				rp = [];

			can.each(args, function (arg, j) {
				arg.done(function () {
					rp[j] = (arguments.length < 2) ? arguments[0] : arguments;
					if (++done == args.length) {
						df.resolve.apply(df, rp);
					}
				}).fail(function () {
					df.reject(arguments);
				});
			});

			return df;

		}
	}

	var resolveFunc = function (type, _status) {
		return function (context) {
			var args = this._resultArgs = (arguments.length > 1) ? arguments[1] : [];
			return this.exec(context, this[type], args, _status);
		}
	},
		doneFunc = function (type, _status) {
			return function () {
				var self = this;
				// In Safari, the properties of the `arguments` object are not enumerable, 
				// so we have to convert arguments to an `Array` that allows `can.each` to loop over them.
				can.each(Array.prototype.slice.call(arguments), function (v, i, args) {
					if (!v) return;
					if (v.constructor === Array) {
						args.callee.apply(self, v)
					} else {
						// Immediately call the `function` if the deferred has been resolved.
						if (self._status === _status) v.apply(self, self._resultArgs || []);

						self[type].push(v);
					}
				});
				return this;
			}
		};

	extend(Deferred.prototype, {
		pipe: function (done, fail) {
			var d = can.Deferred();
			this.done(function () {
				d.resolve(done.apply(this, arguments));
			});

			this.fail(function () {
				if (fail) {
					d.reject(fail.apply(this, arguments));
				} else {
					d.reject.apply(d, arguments);
				}
			});
			return d;
		},
		resolveWith: resolveFunc("_doneFuncs", "rs"),
		rejectWith: resolveFunc("_failFuncs", "rj"),
		done: doneFunc("_doneFuncs", "rs"),
		fail: doneFunc("_failFuncs", "rj"),
		always: function () {
			var args = can.makeArray(arguments);
			if (args.length && args[0]) this.done(args[0]).fail(args[0]);

			return this;
		},

		then: function () {
			var args = can.makeArray(arguments);
			// Fail `function`(s)
			if (args.length > 1 && args[1]) this.fail(args[1]);

			// Done `function`(s)
			if (args.length && args[0]) this.done(args[0]);

			return this;
		},

		state: function () {
			switch (this._status) {
			case 'rs':
				return 'resolved';
			case 'rj':
				return 'rejected';
			default:
				return 'pending';
			}
		},

		isResolved: function () {
			return this._status === "rs";
		},

		isRejected: function () {
			return this._status === "rj";
		},

		reject: function () {
			return this.rejectWith(this, arguments);
		},

		resolve: function () {
			return this.resolveWith(this, arguments);
		},

		exec: function (context, dst, args, st) {
			if (this._status !== "") return this;

			this._status = st;

			can.each(dst, function (d) {
				d.apply(context, args);
			});

			return this;
		}
	});

	return can;
});