blob: 433577850f92d354cbf158d3bacfad1c4698de6f [file] [log] [blame]
// vim:ts=4:sts=4:sw=4:
/*!
*
* Copyright 2007-2009 Tyler Close under the terms of the MIT X license found
* at http://www.opensource.org/licenses/mit-license.html
* Forked at ref_send.js version: 2009-05-11
*
* Copyright 2009-2011 Kris Kowal under the terms of the MIT
* license found at http://github.com/kriskowal/q/raw/master/LICENSE
*
*/
(function (definition, undefined) {
// This file will function properly as a <script> tag, or a module
// using CommonJS and NodeJS or RequireJS module formats. In
// Common/Node/RequireJS, the module exports the Q API and when
// executed as a simple <script>, it creates a Q global instead.
// The use of "undefined" in the arguments is a
// micro-optmization for compression systems, permitting
// every occurrence of the "undefined" variable to be
// replaced with a single-character.
// RequireJS
if (typeof define === "function") {
define(function (require, exports, module) {
definition(require, exports, module);
});
// CommonJS
} else if (typeof exports === "object") {
definition(require, exports, module);
// <script>
} else {
Q = definition(undefined, {}, {});
}
})(function (serverSideRequire, exports, module, undefined) {
"use strict";
var nextTick;
try {
// Narwhal, Node (with a package, wraps process.nextTick)
nextTick = serverSideRequire("event-queue").enqueue;
} catch (e) {
// browsers
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 () {
var next = head.next;
var task = next.task;
head = next;
task();
};
nextTick = function (task) {
tail = tail.next = {task: task};
channel.port2.postMessage();
};
} else {
// old browsers
nextTick = function (task) {
setTimeout(task, 0);
};
}
}
// useful for an identity stub and default resolvers
function identity (x) {return x;}
// shims
var shim = function (object, name, shim) {
if (!object[name])
object[name] = shim;
return object[name];
};
var freeze = shim(Object, "freeze", identity);
var create = shim(Object, "create", function (prototype) {
var Type = function () {};
Type.prototype = prototype;
return new Type();
});
var keys = shim(Object, "keys", function (object) {
var keys = [];
for (var key in object)
keys.push(key);
return keys;
});
var reduce = Array.prototype.reduce || function (callback, basis) {
var i = 0,
ii = this.length;
// concerning the initial value, if one is not provided
if (arguments.length == 1) {
// seek to the first value in the array, accounting
// for the possibility that is is a sparse array
do {
if (i in this) {
basis = this[i++];
break;
}
if (++i >= ii)
throw new TypeError();
} while (1);
}
// reduce
for (; i < ii; i++) {
// account for the possibility that the array is sparse
if (i in this) {
basis = callback(basis, this[i], i);
}
}
return basis;
};
var isStopIteration = function (exception) {
return Object.prototype.toString.call(exception)
=== "[object StopIteration]";
};
// Abbreviations for performance and minification
var slice = Array.prototype.slice;
var nil = null;
var valueOf = function (value) {
if (value === undefined || value === nil) {
return value;
} else {
return value.valueOf();
}
};
/**
* Performs a task in a future turn of the event loop.
* @param {Function} task
*/
exports.enqueue = // XXX enqueue deprecated
exports.nextTick = nextTick;
/**
* Constructs a {promise, resolve} object.
*
* The resolver is a callback to invoke with a more resolved value for the
* promise. To fulfill the promise, invoke the resolver with any value that is
* not a function. To reject the promise, invoke the resolver with a rejection
* object. To put the promise in the same state as another promise, invoke the
* resolver with that other promise.
*/
exports.defer = defer;
function defer() {
// if "pending" is an "Array", that indicates that the promise has not yet
// been resolved. If it is "undefined", it has been resolved. Each
// element of the pending array is itself an array of complete arguments to
// forward to the resolved promise. We coerce the resolution value to a
// promise using the ref promise because it handles both fully
// resolved values and other promises gracefully.
var pending = [], value;
var promise = create(Promise.prototype);
promise.promiseSend = function () {
var args = slice.call(arguments);
if (pending) {
pending.push(args);
} else {
nextTick(function () {
value.promiseSend.apply(value, args);
});
}
};
promise.valueOf = function () {
if (pending)
return promise;
return value.valueOf();
};
var resolve = function (resolvedValue) {
var i, ii, task;
if (!pending)
return;
value = ref(resolvedValue);
reduce.call(pending, function (undefined, pending) {
nextTick(function () {
value.promiseSend.apply(value, pending);
});
}, undefined);
pending = undefined;
return value;
};
return {
"promise": freeze(promise),
"resolve": resolve,
"reject": function (reason) {
return resolve(reject(reason));
}
};
}
/**
* Constructs a Promise with a promise descriptor object and optional fallback
* function. The descriptor contains methods like when(rejected), get(name),
* put(name, value), post(name, args), and delete(name), which all
* return either a value, a promise for a value, or a rejection. The fallback
* accepts the operation name, a resolver, and any further arguments that would
* have been forwarded to the appropriate method above had a method been
* provided with the proper name. The API makes no guarantees about the nature
* of the returned object, apart from that it is usable whereever promises are
* bought and sold.
*/
exports.makePromise = Promise;
function Promise(descriptor, fallback, valueOf) {
if (fallback === undefined) {
fallback = function (op) {
return reject("Promise does not support operation: " + op);
};
}
var promise = create(Promise.prototype);
promise.promiseSend = function (op, resolved /* ...args */) {
var args = slice.call(arguments, 2);
var result;
try {
if (descriptor[op]) {
result = descriptor[op].apply(descriptor, args);
} else {
result = fallback.apply(descriptor, [op].concat(args));
}
} catch (exception) {
result = reject(exception);
}
return (resolved || identity)(result);
};
if (valueOf)
promise.valueOf = valueOf;
return freeze(promise);
};
// provide thenables, CommonJS/Promises/A
Promise.prototype.then = function (fulfilled, rejected) {
return when(this, fulfilled, rejected);
};
// Chainable methods
reduce.call(
[
"when", "send",
"get", "put", "del",
"post", "invoke",
"keys",
"apply", "call",
"all", "wait", "join",
"fail", "fin", "spy", // XXX spy deprecated
"view", "viewInfo",
"timeout", "delay",
"end"
],
function (prev, name) {
Promise.prototype[name] = function () {
return exports[name].apply(
exports,
[this].concat(slice.call(arguments))
);
};
},
undefined
)
Promise.prototype.toSource = function () {
return this.toString();
};
Promise.prototype.toString = function () {
return '[object Promise]';
};
freeze(Promise.prototype);
/**
* @returns whether the given object is a promise.
* Otherwise it is a fulfilled value.
*/
exports.isPromise = isPromise;
function isPromise(object) {
return object && typeof object.promiseSend === "function";
};
/**
* @returns whether the given object is a resolved promise.
*/
exports.isResolved = isResolved;
function isResolved(object) {
return !isPromise(valueOf(object));
};
/**
* @returns whether the given object is a value or fulfilled
* promise.
*/
exports.isFulfilled = isFulfilled;
function isFulfilled(object) {
return !isPromise(valueOf(object)) && !isRejected(object);
};
/**
* @returns whether the given object is a rejected promise.
*/
exports.isRejected = isRejected;
function isRejected(object) {
object = valueOf(object);
if (object === undefined || object === nil)
return false;
return !!object.promiseRejected;
}
/**
* Constructs a rejected promise.
* @param reason value describing the failure
*/
exports.reject = reject;
function reject(reason) {
return Promise({
"when": function (rejected) {
return rejected ? rejected(reason) : reject(reason);
}
}, function fallback(op) {
return reject(reason);
}, function valueOf() {
var rejection = create(reject.prototype);
rejection.promiseRejected = true;
rejection.reason = reason;
return rejection;
});
}
reject.prototype = create(Promise.prototype, {
constructor: { value: reject }
});
/**
* Constructs a promise for an immediate reference.
* @param value immediate reference
*/
exports.ref = ref;
function ref(object) {
// If the object is already a Promise, return it directly. This enables
// the ref function to both be used to created references from
// objects, but to tolerably coerce non-promises to refs if they are
// not already Promises.
if (isPromise(object))
return object;
// assimilate thenables, CommonJS/Promises/A
if (object && typeof object.then === "function") {
var result = defer();
object.then(result.resolve, result.reject);
return result.promise;
}
return Promise({
"when": function (rejected) {
return object;
},
"get": function (name) {
return object[name];
},
"put": function (name, value) {
return object[name] = value;
},
"del": function (name) {
return delete object[name];
},
"post": function (name, value) {
return object[name].apply(object, value);
},
"apply": function (self, args) {
return object.apply(self, args);
},
"viewInfo": function () {
var on = object;
var properties = {};
while (on) {
Object.getOwnPropertyNames(on).forEach(function (name) {
if (!properties[name])
properties[name] = typeof on[name];
});
on = Object.getPrototypeOf(on);
}
return {
"type": typeof object,
"properties": properties
}
},
"keys": function () {
return keys(object);
}
}, undefined, function valueOf() {
return object;
});
}
/**
* Annotates an object such that it will never be
* transferred away from this process over any promise
* communication channel.
* @param object
* @returns promise a wrapping of that object that
* additionally responds to the 'isDef' message
* without a rejection.
*/
exports.master =
exports.def = def;
function def(object) {
return Promise({
"isDef": function () {}
}, function fallback(op) {
var args = slice.call(arguments);
return send.apply(undefined, [object].concat(args));
}, function () {
return valueOf(object);
});
}
exports.viewInfo = viewInfo;
function viewInfo(object, info) {
object = ref(object);
if (info) {
return Promise({
"viewInfo": function () {
return info;
}
}, function fallback(op) {
var args = slice.call(arguments);
return send.apply(undefined, [object].concat(args));
}, function () {
return valueOf(object);
});
} else {
return send(object, "viewInfo")
}
}
exports.view = function (object) {
return viewInfo(object).when(function (info) {
var view;
if (info.type === "function") {
view = function () {
return apply(object, undefined, arguments);
};
} else {
view = {};
}
var properties = info.properties || {};
Object.keys(properties).forEach(function (name) {
if (properties[name] === "function") {
view[name] = function () {
return post(object, name, arguments);
};
}
});
return ref(view);
});
};
/**
* Registers an observer on a promise.
*
* Guarantees:
*
* 1. that fulfilled and rejected will be called only once.
* 2. that either the fulfilled callback or the rejected callback will be
* called, but not both.
* 3. that fulfilled and rejected will not be called in this turn.
*
* @param value promise or immediate reference to observe
* @param fulfilled function to be called with the fulfilled value
* @param rejected function to be called with the rejection reason
* @return promise for the return value from the invoked callback
*/
exports.when = when;
function when(value, fulfilled, rejected) {
var deferred = defer();
var done = false; // ensure the untrusted promise makes at most a
// single call to one of the callbacks
function _fulfilled(value) {
try {
return fulfilled ? fulfilled(value) : value;
} catch (exception) {
return reject(exception);
}
}
function _rejected(reason) {
try {
return rejected ? rejected(reason) : reject(reason);
} catch (exception) {
return reject(exception);
}
}
nextTick(function () {
ref(value).promiseSend("when", function (value) {
if (done)
return;
done = true;
deferred.resolve(
ref(value)
.promiseSend("when", _fulfilled, _rejected)
);
}, function (reason) {
if (done)
return;
done = true;
deferred.resolve(_rejected(reason));
});
});
return deferred.promise;
}
/**
* The async function is a decorator for generator functions, turning
* them into asynchronous generators. This presently only works in
* Firefox/Spidermonkey, however, this code does not cause syntax
* errors in older engines. This code should continue to work and
* will in fact improve over time as the language improves.
*
* Decorates a generator function such that:
* - it may yield promises
* - execution will continue when that promise is fulfilled
* - the value of the yield expression will be the fulfilled value
* - it returns a promise for the return value (when the generator
* stops iterating)
* - the decorated function returns a promise for the return value
* of the generator or the first rejected promise among those
* yielded.
* - if an error is thrown in the generator, it propagates through
* every following yield until it is caught, or until it escapes
* the generator function altogether, and is translated into a
* rejection for the promise returned by the decorated generator.
* - in present implementations of generators, when a generator
* function is complete, it throws ``StopIteration``, ``return`` is
* a syntax error in the presence of ``yield``, so there is no
* observable return value. There is a proposal[1] to add support
* for ``return``, which would permit the value to be carried by a
* ``StopIteration`` instance, in which case it would fulfill the
* promise returned by the asynchronous generator. This can be
* emulated today by throwing StopIteration explicitly with a value
* property.
*
* [1]: http://wiki.ecmascript.org/doku.php?id=strawman:async_functions#reference_implementation
*
*/
exports.async = async;
function async(makeGenerator) {
return function () {
// when verb is "send", arg is a value
// when verb is "throw", arg is a reason/error
var continuer = function (verb, arg) {
var result;
try {
result = generator[verb](arg);
} catch (exception) {
if (isStopIteration(exception)) {
return exception.value;
} else {
return reject(exception);
}
}
return when(result, callback, errback);
};
var generator = makeGenerator.apply(this, arguments);
var callback = continuer.bind(continuer, "send");
var errback = continuer.bind(continuer, "throw");
return callback();
};
}
/**
* Constructs a promise method that can be used to safely observe resolution of
* a promise for an arbitrarily named method like "propfind" in a future turn.
*
* "Method" constructs methods like "get(promise, name)" and "put(promise)".
*/
exports.Method = Method;
function Method (op) {
return function (object) {
var args = slice.call(arguments, 1);
return send.apply(undefined, [object, op].concat(args));
};
}
/**
* sends a message to a value in a future turn
* @param object* the recipient
* @param op the name of the message operation, e.g., "when",
* @param ...args further arguments to be forwarded to the operation
* @returns result {Promise} a promise for the result of the operation
*/
exports.send = send;
function send(object, op) {
var deferred = defer();
var args = slice.call(arguments, 2);
object = ref(object);
nextTick(function () {
object.promiseSend.apply(
object,
[op, deferred.resolve].concat(args)
);
});
return deferred.promise;
}
/**
* Gets the value of a property in a future turn.
* @param object promise or immediate reference for target object
* @param name name of property to get
* @return promise for the property value
*/
exports.get = Method("get");
/**
* Sets the value of a property in a future turn.
* @param object promise or immediate reference for object object
* @param name name of property to set
* @param value new value of property
* @return promise for the return value
*/
exports.put = Method("put");
/**
* Deletes a property in a future turn.
* @param object promise or immediate reference for target object
* @param name name of property to delete
* @return promise for the return value
*/
exports.del = Method("del");
/**
* Invokes a method in a future turn.
* @param object promise or immediate reference for target object
* @param name name of method to invoke
* @param value a value to post, typically an array of
* invocation arguments for promises that
* are ultimately backed with `ref` values,
* as opposed to those backed with URLs
* wherein the posted value can be any
* JSON serializable object.
* @return promise for the return value
*/
var post = exports.post = Method("post");
/**
* Invokes a method in a future turn.
* @param object promise or immediate reference for target object
* @param name name of method to invoke
* @param ...args array of invocation arguments
* @return promise for the return value
*/
exports.invoke = function (value, name) {
var args = slice.call(arguments, 2);
return post(value, name, args);
};
/**
* Applies the promised function in a future turn.
* @param object promise or immediate reference for target function
* @param context the context object (this) for the call
* @param args array of application arguments
*/
var apply = exports.apply = Method("apply");
/**
* Calls the promised function in a future turn.
* @param object promise or immediate reference for target function
* @param context the context object (this) for the call
* @param ...args array of application arguments
*/
exports.call = function (value, context) {
var args = slice.call(arguments, 2);
return apply(value, context, args);
};
/**
* Requests the names of the owned properties of a promised
* object in a future turn.
* @param object promise or immediate reference for target object
* @return promise for the keys of the eventually resolved object
*/
exports.keys = Method("keys");
// By Mark Miller
// http://wiki.ecmascript.org/doku.php?id=strawman:concurrency&rev=1308776521#allfulfilled
exports.all = all;
function all(promises) {
return when(promises, function (promises) {
var countDown = promises.length;
var values = [];
if (countDown === 0)
return ref(values);
var deferred = defer();
reduce.call(promises, function (undefined, promise, index) {
when(promise, function (answer) {
values[index] = answer;
if (--countDown === 0)
deferred.resolve(values);
}, deferred.reject);
}, undefined);
return deferred.promise;
});
}
/**
*/
exports.wait = function (promise) {
return all(arguments).get(0);
};
/**
*/
exports.join = function () {
var args = slice.call(arguments);
var callback = args.pop();
return all(args).then(function (args) {
return callback.apply(undefined, args);
});
};
/**
*/
exports.fail = fail;
function fail(promise, rejected) {
return when(promise, undefined, rejected);
}
/**
*/
exports.spy = // XXX spy deprecated
exports.fin = fin;
function fin(promise, callback) {
return when(promise, function (value) {
return when(callback(), function () {
return value;
});
}, function (reason) {
return when(callback(), function () {
return reject(reason);
});
});
}
/**
* Terminates a chain of promises, forcing rejections to be
* thrown as exceptions.
*/
exports.end = end;
function end(promise) {
when(promise, undefined, function (error) {
// forward to a future turn so that ``when``
// does not catch it and turn it into a rejection.
nextTick(function () {
throw error;
});
});
}
/**
*/
exports.timeout = timeout;
function timeout(promise, timeout) {
var deferred = defer();
when(promise, deferred.resolve, deferred.reject);
setTimeout(function () {
deferred.reject("Timed out");
}, timeout);
return deferred.promise;
}
/**
*/
exports.delay = delay;
function delay(promise, timeout) {
var deferred = defer();
setTimeout(function () {
deferred.resolve(promise);
}, timeout);
return deferred.promise;
}
/*
* In module systems that support ``module.exports`` assignment or exports
* return, allow the ``ref`` function to be used as the ``Q`` constructor
* exported by the "q" module.
*/
for (var name in exports)
ref[name] = exports[name];
module.exports = ref;
return ref;
});