www

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | Submodules | README | LICENSE

commit c4cb78331797998db17784c15bf520a553676d89
parent 4e5d4281f1b197dcdf1368e6d879735f861243b4
Author: Simon Kornblith <simon@simonster.com>
Date:   Sun,  3 Jun 2012 00:43:42 -0400

Add q promise library (as jsm)

Diffstat:
Aresource/q.jsm | 1494+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 1494 insertions(+), 0 deletions(-)

diff --git a/resource/q.jsm b/resource/q.jsm @@ -0,0 +1,1494 @@ +// vim:ts=4:sts=4:sw=4: +/*jshint browser: true, node: true, + curly: true, eqeqeq: true, noarg: true, nonew: true, trailing: true, + undef: true */ +/*global define: false, Q: true, msSetImmediate: false, setImmediate: false, + ReturnValue: false, cajaVM: false, ses: false */ +/*! + * + * Copyright 2009-2012 Kris Kowal under the terms of the MIT + * license found at http://github.com/kriskowal/q/raw/master/LICENSE + * + * With parts by Tyler Close + * 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 + * + * With parts by Mark Miller + * Copyright (C) 2011 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * With formatStackTrace and formatSourcePosition functions + * Copyright 2006-2008 the V8 project authors. All rights reserved. + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +var EXPORTED_SYMBOLS = ["Q"]; +var setTimeout = new function() { + var _runningTimers = []; + return function setTimeout(func, ms) { + var timer = Components.classes["@mozilla.org/timer;1"]. + createInstance(Components.interfaces.nsITimer); + var timerCallback = {"notify":function() { + // remove timer from global scope, so it can be garbage collected + _runningTimers.splice(_runningTimers.indexOf(timer), 1); + // execute callback function + try { + func(); + } catch(err) { + var scriptError = Components.classes["@mozilla.org/scripterror;1"] + .createInstance(Components.interfaces.nsIScriptError); + scriptError.init( + err.message ? err.message : err.toString(), + err.fileName ? err.fileName : (err.filename ? err.filename : null), + null, + err.lineNumber ? err.lineNumber : null, + null, + scriptError['errorFlag'], + 'component javascript' + ); + Components.classes["@mozilla.org/consoleservice;1"] + .getService(Components.interfaces.nsIConsoleService) + .logMessage(scriptError); + } + }}; + timer.initWithCallback(timerCallback, ms, Components.interfaces.nsITimer.TYPE_ONE_SHOT); + } +}; + +(function (definition) { + // Turn off strict mode for this function so we can assign to global.Q + /*jshint strict: false*/ + + // 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. + + // RequireJS + if (typeof define === "function") { + define(definition); + + // CommonJS + } else if (typeof exports === "object") { + definition(void 0, exports); + + // SES (Secure EcmaScript) + } else if (typeof ses !== "undefined") { + if (!ses.ok()) { + return; + } else { + ses.makeQ = function () { + var Q = {}; + return definition(void 0, Q); + }; + } + + // <script> + } else { + definition(void 0, Q = {}); + } + +})(function (require, exports) { +"use strict"; + +// shims + +// used for fallback "defend" and in "allResolved" +var noop = function () {}; + +// for the security conscious, defend may be a deep freeze as provided +// by cajaVM. Otherwise we try to provide a shallow freeze just to +// discourage promise changes that are not compatible with secure +// usage. If Object.freeze does not exist, fall back to doing nothing +// (no op). +var defend = Object.freeze || noop; +if (typeof cajaVM !== "undefined") { + defend = cajaVM.def; +} + +// use the fastest possible means to execute a task in a future turn +// of the event loop. +var nextTick; +if (typeof process !== "undefined") { + // node + nextTick = process.nextTick; +} else if (typeof msSetImmediate === "function") { + // IE 10 only, at the moment + // And yes, ``bind``ing to ``window`` is necessary O_o. + nextTick = msSetImmediate.bind(window); +} else if (typeof setImmediate === "function") { + // https://github.com/NobleJS/setImmediate + nextTick = setImmediate; +} 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); + }; +} + +// Attempt to make generics safe in the face of downstream +// modifications. +// There is no situation where this is necessary. +// If you need a security guarantee, these primordials need to be +// deeply frozen anyway, and if you don’t need a security guarantee, +// this is just plain paranoid. +// However, this does have the nice side-effect of reducing the size +// of the code by reducing x.call() to merely x(), eliminating many +// hard-to-minify characters. +// See Mark Miller’s explanation of what this does. +// http://wiki.ecmascript.org/doku.php?id=conventions:safe_meta_programming +var uncurryThis; +// I have kept both variations because the first is theoretically +// faster, if bind is available. +if (Function.prototype.bind) { + var Function_bind = Function.prototype.bind; + uncurryThis = Function_bind.bind(Function_bind.call); +} else { + uncurryThis = function (f) { + return function (thisp) { + return f.call.apply(f, arguments); + }; + }; +} + +var array_slice = uncurryThis(Array.prototype.slice); + +var array_reduce = uncurryThis( + Array.prototype.reduce || function (callback, basis) { + var index = 0, + length = 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 (index in this) { + basis = this[index++]; + break; + } + if (++index >= length) { + throw new TypeError(); + } + } while (1); + } + // reduce + for (; index < length; index++) { + // account for the possibility that the array is sparse + if (index in this) { + basis = callback(basis, this[index], index); + } + } + return basis; + } +); + +var array_indexOf = uncurryThis( + Array.prototype.indexOf || function (value) { + // not a very good shim, but good enough for our one use of it + for (var i = 0; i < this.length; i++) { + if (this[i] === value) { + return i; + } + } + return -1; + } +); + +var array_map = uncurryThis( + Array.prototype.map || function (callback, thisp) { + var self = this; + var collect = []; + array_reduce(self, function (undefined, value, index) { + collect.push(callback.call(thisp, value, index, self)); + }, void 0); + return collect; + } +); + +var object_create = Object.create || function (prototype) { + function Type() { } + Type.prototype = prototype; + return new Type(); +}; + +var object_keys = Object.keys || function (object) { + var keys = []; + for (var key in object) { + keys.push(key); + } + return keys; +}; + +var object_toString = Object.prototype.toString; + +// generator related shims + +function isStopIteration(exception) { + return ( + object_toString(exception) === "[object StopIteration]" || + exception instanceof QReturnValue + ); +} + +var QReturnValue; +if (typeof ReturnValue !== "undefined") { + QReturnValue = ReturnValue; +} else { + QReturnValue = function (value) { + this.value = value; + }; +} + +// long stack traces + +function formatStackTrace(error, frames) { + var lines = []; + try { + lines.push(error.toString()); + } catch (e) { + try { + lines.push("<error: " + e + ">"); + } catch (ee) { + lines.push("<error>"); + } + } + for (var i = 0; i < frames.length; i++) { + var frame = frames[i]; + var line; + + // <Inserted by @domenic> + if (typeof frame === "string") { + lines.push(frame); + // </Inserted by @domenic> + } else { + try { + line = formatSourcePosition(frame); + } catch (e) { + try { + line = "<error: " + e + ">"; + } catch (ee) { + // Any code that reaches this point is seriously nasty! + line = "<error>"; + } + } + lines.push(" at " + line); + } + } + return lines.join("\n"); +} + +function formatSourcePosition(frame) { + var fileLocation = ""; + if (frame.isNative()) { + fileLocation = "native"; + } else if (frame.isEval()) { + fileLocation = "eval at " + frame.getEvalOrigin(); + } else { + var fileName = frame.getFileName(); + if (fileName) { + fileLocation += fileName; + var lineNumber = frame.getLineNumber(); + if (lineNumber !== null) { + fileLocation += ":" + lineNumber; + var columnNumber = frame.getColumnNumber(); + if (columnNumber) { + fileLocation += ":" + columnNumber; + } + } + } + } + if (!fileLocation) { + fileLocation = "unknown source"; + } + var line = ""; + var functionName = frame.getFunction().name; + var addPrefix = true; + var isConstructor = frame.isConstructor(); + var isMethodCall = !(frame.isToplevel() || isConstructor); + if (isMethodCall) { + var methodName = frame.getMethodName(); + line += frame.getTypeName() + "."; + if (functionName) { + line += functionName; + if (methodName && (methodName !== functionName)) { + line += " [as " + methodName + "]"; + } + } else { + line += methodName || "<anonymous>"; + } + } else if (isConstructor) { + line += "new " + (functionName || "<anonymous>"); + } else if (functionName) { + line += functionName; + } else { + line += fileLocation; + addPrefix = false; + } + if (addPrefix) { + line += " (" + fileLocation + ")"; + } + return line; +} + +/* + * Retrieves an array of structured stack frames parsed from the ``stack`` + * property of a given object. + * + * @param objectWithStack {Object} an object with a ``stack`` property: usually + * an error or promise. + * + * @returns an array of stack frame objects. For more information, see + * [V8's JavaScript stack trace API documentation](http://code.google.com/p/v8/wiki/JavaScriptStackTraceApi). + */ +function getStackFrames(objectWithStack) { + var oldPrepareStackTrace = Error.prepareStackTrace; + + Error.prepareStackTrace = function (error, frames) { + // Filter out frames from the innards of Node and Q. + return frames.filter(function (frame) { + var fileName = frame.getFileName(); + return ( + fileName !== "module.js" && + fileName !== "node.js" && + fileName !== qFileName + ); + }); + }; + + var stack = objectWithStack.stack; + + Error.prepareStackTrace = oldPrepareStackTrace; + + return stack; +} + +// discover own file name for filtering stack traces +var qFileName; +if (Error.captureStackTrace) { + qFileName = (function () { + var fileName; + + var oldPrepareStackTrace = Error.prepareStackTrace; + + Error.prepareStackTrace = function (error, frames) { + fileName = frames[0].getFileName(); + }; + + // teases call of temporary prepareStackTrace + // JSHint and Closure Compiler generate known warnings here + /*jshint expr: true */ + new Error().stack; + + Error.prepareStackTrace = oldPrepareStackTrace; + + return fileName; + })(); +} + +function deprecate(fn, name, alternative){ + return function () { + if (typeof console !== "undefined" && typeof console.warn === "function"){ + console.warn(name + " is deprecated, use " + alternative + " instead."); + } + return fn.apply(fn,arguments); + }; +} + +// end of shims +// beginning of real work + +/** + * Performs a task in a future turn of the event loop. + * @param {Function} task + */ +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 deferred = object_create(defer.prototype); + var promise = object_create(makePromise.prototype); + + promise.promiseSend = function () { + var args = array_slice(arguments); + if (pending) { + pending.push(args); + } else { + nextTick(function () { + value.promiseSend.apply(value, args); + }); + } + }; + + promise.valueOf = function () { + if (pending) { + return promise; + } + return value.valueOf(); + }; + + if (Error.captureStackTrace) { + Error.captureStackTrace(promise, defer); + } + + function become(resolvedValue) { + if (!pending) { + return; + } + value = resolve(resolvedValue); + array_reduce(pending, function (undefined, pending) { + nextTick(function () { + value.promiseSend.apply(value, pending); + }); + }, void 0); + pending = void 0; + return value; + } + + defend(promise); + + deferred.promise = promise; + deferred.resolve = become; + deferred.reject = function (exception) { + return become(reject(exception)); + }; + + return deferred; +} + +/** + * Creates a Node-style callback that will resolve or reject the deferred + * promise. + * @returns a nodeback + */ +defer.prototype.makeNodeResolver = function () { + var self = this; + return function (error, value) { + if (error) { + self.reject(error); + } else if (arguments.length > 2) { + self.resolve(array_slice(arguments, 1)); + } else { + self.resolve(value); + } + }; +}; +// XXX deprecated +defer.prototype.node = deprecate(defer.prototype.makeNodeResolver, "node", "makeNodeResolver"); + +/** + * @param makePromise {Function} a function that returns nothing and accepts + * the resolve and reject functions for a deferred. + * @returns a promise that may be resolved with the given resolve and reject + * functions, or rejected by a thrown exception in makePromise + */ +exports.promise = promise; +function promise(makePromise) { + var deferred = defer(); + call( + makePromise, + void 0, + deferred.resolve, + deferred.reject + ).fail(deferred.reject); + return deferred.promise; +} + +/** + * 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 = makePromise; +function makePromise(descriptor, fallback, valueOf, exception) { + if (fallback === void 0) { + fallback = function (op) { + return reject(new Error("Promise does not support operation: " + op)); + }; + } + + var promise = object_create(makePromise.prototype); + + promise.promiseSend = function (op, resolved /* ...args */) { + var args = array_slice(arguments, 2); + var result; + try { + if (descriptor[op]) { + result = descriptor[op].apply(promise, args); + } else { + result = fallback.apply(promise, [op].concat(args)); + } + } catch (exception) { + result = reject(exception); + } + resolved(result); + }; + + if (valueOf) { + promise.valueOf = valueOf; + } + + if (exception) { + promise.exception = exception; + } + + defend(promise); + + return promise; +} + +// provide thenables, CommonJS/Promises/A +makePromise.prototype.then = function (fulfilled, rejected) { + return when(this, fulfilled, rejected); +}; + +// Chainable methods +array_reduce( + [ + "isResolved", "isFulfilled", "isRejected", + "when", "spread", "send", + "get", "put", "del", + "post", "invoke", + "keys", + "apply", "call", "bind", + "fapply", "fcall", "fbind", + "all", "allResolved", + "view", "viewInfo", + "timeout", "delay", + "catch", "finally", "fail", "fin", "end" + ], + function (prev, name) { + makePromise.prototype[name] = function () { + return exports[name].apply( + exports, + [this].concat(array_slice(arguments)) + ); + }; + }, + void 0 +); + +makePromise.prototype.toSource = function () { + return this.toString(); +}; + +makePromise.prototype.toString = function () { + return "[object Promise]"; +}; + +defend(makePromise.prototype); + +/** + * If an object is not a promise, it is as "near" as possible. + * If a promise is rejected, it is as "near" as possible too. + * If it’s a fulfilled promise, the fulfillment value is nearer. + * If it’s a deferred promise and the deferred has been resolved, the + * resolution is "nearer". + * @param object + * @returns most resolved (nearest) form of the object + */ +exports.nearer = valueOf; +function valueOf(value) { + // if !Object.isObject(value) + // generates a known JSHint "constructor invocation without new" warning + // supposed to be fixed, but isn't? https://github.com/jshint/jshint/issues/392 + /*jshint newcap: false */ + if (Object(value) !== value) { + return value; + } else { + return value.valueOf(); + } +} + +/** + * @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 isFulfilled(object) || isRejected(object); +} + +/** + * @returns whether the given object is a value or fulfilled + * promise. + */ +exports.isFulfilled = isFulfilled; +function isFulfilled(object) { + return !isPromise(valueOf(object)); +} + +/** + * @returns whether the given object is a rejected promise. + */ +exports.isRejected = isRejected; +function isRejected(object) { + object = valueOf(object); + return isPromise(object) && 'exception' in object; +} + +var rejections = []; +var errors = []; +if (typeof window !== "undefined" && window.console) { + // This promise library consumes exceptions thrown in handlers so + // they can be handled by a subsequent promise. The rejected + // promises get added to this array when they are created, and + // removed when they are handled. + console.log("Should be empty:", errors); +} + +/** + * Constructs a rejected promise. + * @param exception value describing the failure + */ +exports.reject = reject; +function reject(exception) { + exception = exception || new Error(); + var rejection = makePromise({ + "when": function (rejected) { + // note that the error has been handled + if (rejected) { + var at = array_indexOf(rejections, this); + if (at !== -1) { + errors.splice(at, 1); + rejections.splice(at, 1); + } + } + return rejected ? rejected(exception) : reject(exception); + } + }, function fallback(op) { + return reject(exception); + }, function valueOf() { + return this; + }, exception); + // note that the error has not been handled + rejections.push(rejection); + errors.push(exception); + return rejection; +} + +/** + * Constructs a promise for an immediate reference. + * @param value immediate reference + */ +exports.begin = resolve; // XXX experimental +exports.resolve = resolve; +exports.ref = deprecate(resolve, "ref", "resolve"); // XXX deprecated, use resolve +function resolve(object) { + // If the object is already a Promise, return it directly. This enables + // the resolve function to both be used to created references from objects, + // but to tolerably coerce non-promises to 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 makePromise({ + "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); + }, + "fapply": function (args) { + return object.apply(void 0, args); + }, + "viewInfo": function () { + var on = object; + var properties = {}; + + function fixFalsyProperty(name) { + if (!properties[name]) { + properties[name] = typeof on[name]; + } + } + + while (on) { + Object.getOwnPropertyNames(on).forEach(fixFalsyProperty); + on = Object.getPrototypeOf(on); + } + return { + "type": typeof object, + "properties": properties + }; + }, + "keys": function () { + return object_keys(object); + } + }, void 0, 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 = master; +function master(object) { + return makePromise({ + "isDef": function () {} + }, function fallback(op) { + var args = array_slice(arguments); + return send.apply(void 0, [object].concat(args)); + }, function () { + return valueOf(object); + }); +} + +exports.viewInfo = viewInfo; +function viewInfo(object, info) { + object = resolve(object); + if (info) { + return makePromise({ + "viewInfo": function () { + return info; + } + }, function fallback(op) { + var args = array_slice(arguments); + return send.apply(void 0, [object].concat(args)); + }, function () { + return valueOf(object); + }); + } else { + return send(object, "viewInfo"); + } +} + +exports.view = view; +function view(object) { + return viewInfo(object).when(function (info) { + var view; + if (info.type === "function") { + view = function () { + return apply(object, void 0, 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 resolve(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 exception + * @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(exception) { + try { + return rejected ? rejected(exception) : reject(exception); + } catch (newException) { + return reject(newException); + } + } + + nextTick(function () { + resolve(value).promiseSend("when", function (value) { + if (done) { + return; + } + done = true; + resolve(value).promiseSend("when", function (value) { + deferred.resolve(_fulfilled(value)); + }, function (exception) { + deferred.resolve(_rejected(exception)); + }); + }, function (exception) { + if (done) { + return; + } + done = true; + deferred.resolve(_rejected(exception)); + }); + }); + + return deferred.promise; +} + +/** + * Spreads the values of a promised array of arguments into the + * fulfillment callback. + * @param fulfilled callback that receives variadic arguments from the + * promised array + * @param rejected callback that receives the exception if the promise + * is rejected. + * @returns a promise for the return value or thrown exception of + * either callback. + */ +exports.spread = spread; +function spread(promise, fulfilled, rejected) { + return when(promise, function (values) { + return fulfilled.apply(void 0, values); + }, rejected); +} + +/** + * 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 an exception + function continuer(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(); + }; +} + +/** + * Throws a ReturnValue exception to stop an asynchronous generator. + * Only useful presently in Firefox/SpiderMonkey since generators are + * implemented. + * @param value the return value for the surrounding generator + * @throws ReturnValue exception with the value. + * @example + * Q.async(function () { + * var foo = yield getFooPromise(); + * var bar = yield getBarPromise(); + * Q.return(foo + bar); + * }) + */ +exports['return'] = _return; +function _return(value) { + throw new QReturnValue(value); +} + +/** + * 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. + */ +exports.sender = deprecate(sender, "sender", "dispatcher"); // XXX deprecated, use dispatcher +exports.Method = deprecate(sender, "Method", "dispatcher"); // XXX deprecated, use dispatcher +function sender(op) { + return function (object) { + var args = array_slice(arguments, 1); + return send.apply(void 0, [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 = deprecate(send, "send", "dispatch"); // XXX deprecated, use dispatch +function send(object, op) { + var deferred = defer(); + var args = array_slice(arguments, 2); + object = resolve(object); + nextTick(function () { + object.promiseSend.apply( + object, + [op, deferred.resolve].concat(args) + ); + }); + return deferred.promise; +} + +/** + * 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.dispatch = dispatch; +function dispatch(object, op, args) { + var deferred = defer(); + object = resolve(object); + nextTick(function () { + object.promiseSend.apply( + object, + [op, deferred.resolve].concat(args) + ); + }); + return deferred.promise; +} + +/** + * 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. + * + * "dispatcher" constructs methods like "get(promise, name)" and "put(promise)". + */ +exports.dispatcher = dispatcher; +function dispatcher(op) { + return function (object) { + var args = array_slice(arguments, 1); + return dispatch(object, op, args); + }; +} + +/** + * 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 = dispatcher("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 = dispatcher("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["delete"] = // XXX experimental +exports.del = dispatcher("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 `resolve` values, + * as opposed to those backed with URLs + * wherein the posted value can be any + * JSON serializable object. + * @return promise for the return value + */ +// bound locally because it is used by other methods +var post = exports.post = dispatcher("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 = array_slice(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 thisp the `this` object for the call + * @param args array of application arguments + */ +// XXX deprecated, use fapply +var apply = exports.apply = deprecate(dispatcher("apply"), "apply", "fapply"); + +/** + * Applies the promised function in a future turn. + * @param object promise or immediate reference for target function + * @param args array of application arguments + */ +var fapply = exports.fapply = dispatcher("fapply"); + +/** + * Calls the promised function in a future turn. + * @param object promise or immediate reference for target function + * @param thisp the `this` object for the call + * @param ...args array of application arguments + */ +// XXX deprecated, use fcall +exports.call = deprecate(call, "call", "fcall"); +function call(value, thisp) { + var args = array_slice(arguments, 2); + return apply(value, thisp, args); +} + +/** + * Calls the promised function in a future turn. + * @param object promise or immediate reference for target function + * @param ...args array of application arguments + */ +exports["try"] = fcall; // XXX experimental +exports.fcall = fcall; +function fcall(value) { + var args = array_slice(arguments, 1); + return fapply(value, args); +} + +/** + * Binds the promised function, transforming return values into a fulfilled + * promise and thrown errors into a rejected one. + * @param object promise or immediate reference for target function + * @param thisp the `this` object for the call + * @param ...args array of application arguments + */ +exports.bind = deprecate(bind, "bind", "fbind"); // XXX deprecated, use fbind +function bind(value, thisp) { + var args = array_slice(arguments, 2); + return function bound() { + var allArgs = args.concat(array_slice(arguments)); + return apply(value, thisp, allArgs); + }; +} + +/** + * Binds the promised function, transforming return values into a fulfilled + * promise and thrown errors into a rejected one. + * @param object promise or immediate reference for target function + * @param ...args array of application arguments + */ +exports.fbind = fbind; +function fbind(value) { + var args = array_slice(arguments, 1); + return function fbound() { + var allArgs = args.concat(array_slice(arguments)); + return fapply(value, allArgs); + }; +} + +/** + * 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 = dispatcher("keys"); + +/** + * Turns an array of promises into a promise for an array. If any of + * the promises gets rejected, the whole array is rejected immediately. + * @param {Array*} an array (or promise for an array) of values (or + * promises for values) + * @returns a promise for an array of the corresponding values + */ +// 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; + if (countDown === 0) { + return resolve(promises); + } + var deferred = defer(); + array_reduce(promises, function (undefined, promise, index) { + when(promise, function (value) { + promises[index] = value; + if (--countDown === 0) { + deferred.resolve(promises); + } + }) + .fail(deferred.reject); + }, void 0); + return deferred.promise; + }); +} + +/** + * Waits for all promises to be resolved, either fulfilled or + * rejected. This is distinct from `all` since that would stop + * waiting at the first rejection. The promise returned by + * `allResolved` will never be rejected. + * @param promises a promise for an array (or an array) of promises + * (or values) + * @return a promise for an array of promises + */ +exports.allResolved = allResolved; +function allResolved(promises) { + return when(promises, function (promises) { + return when(all(array_map(promises, function (promise) { + return when(promise, noop, noop); + })), function () { + return array_map(promises, resolve); + }); + }); +} + +/** + * Captures the failure of a promise, giving an oportunity to recover + * with a callback. If the given promise is fulfilled, the returned + * promise is fulfilled. + * @param {Any*} promise for something + * @param {Function} callback to fulfill the returned promise if the + * given promise is rejected + * @returns a promise for the return value of the callback + */ +exports["catch"] = // XXX experimental +exports.fail = fail; +function fail(promise, rejected) { + return when(promise, void 0, rejected); +} + +/** + * Provides an opportunity to observe the rejection of a promise, + * regardless of whether the promise is fulfilled or rejected. Forwards + * the resolution to the returned promise when the callback is done. + * The callback can return a promise to defer completion. + * @param {Any*} promise + * @param {Function} callback to observe the resolution of the given + * promise, takes no arguments. + * @returns a promise for the resolution of the given promise when + * ``fin`` is done. + */ +exports["finally"] = // XXX experimental +exports.fin = fin; +function fin(promise, callback) { + return when(promise, function (value) { + return when(callback(), function () { + return value; + }); + }, function (exception) { + return when(callback(), function () { + return reject(exception); + }); + }); +} + +/** + * Terminates a chain of promises, forcing rejections to be + * thrown as exceptions. + * @param {Any*} promise at the end of a chain of promises + * @returns nothing + */ +exports.end = end; // XXX stopgap +function end(promise) { + when(promise, void 0, function (error) { + // forward to a future turn so that ``when`` + // does not catch it and turn it into a rejection. + nextTick(function () { + // If possible (that is, if in V8), transform the error stack + // trace by removing Node and Q cruft, then concatenating with + // the stack trace of the promise we are ``end``ing. See #57. + if (Error.captureStackTrace && "stack" in error) { + var errorStackFrames = getStackFrames(error); + var promiseStackFrames = getStackFrames(promise); + + var combinedStackFrames = errorStackFrames.concat( + "From previous event:", + promiseStackFrames + ); + error.stack = formatStackTrace(error, combinedStackFrames); + } + + throw error; + }); + }); +} + +/** + * Causes a promise to be rejected if it does not get fulfilled before + * some milliseconds time out. + * @param {Any*} promise + * @param {Number} milliseconds timeout + * @returns a promise for the resolution of the given promise if it is + * fulfilled before the timeout, otherwise rejected. + */ +exports.timeout = timeout; +function timeout(promise, ms) { + var deferred = defer(); + when(promise, deferred.resolve, deferred.reject); + setTimeout(function () { + deferred.reject(new Error("Timed out after " + ms + "ms")); + }, ms); + return deferred.promise; +} + +/** + * Returns a promise for the given value (or promised value) after some + * milliseconds. + * @param {Any*} promise + * @param {Number} milliseconds + * @returns a promise for the resolution of the given promise after some + * time has elapsed. + */ +exports.delay = delay; +function delay(promise, timeout) { + if (timeout === void 0) { + timeout = promise; + promise = void 0; + } + var deferred = defer(); + setTimeout(function () { + deferred.resolve(promise); + }, timeout); + return deferred.promise; +} + +/** + * Passes a continuation to a Node function, which is called with a given + * `this` value and arguments provided as an array, and returns a promise. + * + * var FS = require("fs"); + * Q.napply(FS.readFile, FS, [__filename]) + * .then(function (content) { + * }) + * + */ +exports.napply = napply; +function napply(callback, thisp, args) { + return nbind(callback, thisp).apply(void 0, args); +} + +/** + * Passes a continuation to a Node function, which is called with a given + * `this` value and arguments provided individually, and returns a promise. + * + * var FS = require("fs"); + * Q.ncall(FS.readFile, FS, __filename) + * .then(function (content) { + * }) + * + */ +exports.ncall = ncall; +function ncall(callback, thisp /*, ...args*/) { + var args = array_slice(arguments, 2); + return napply(callback, thisp, args); +} + +/** + * Wraps a NodeJS continuation passing function and returns an equivalent + * version that returns a promise. + * + * Q.nbind(FS.readFile, FS)(__filename) + * .then(console.log) + * .end() + * + */ +exports.nbind = nbind; +function nbind(callback /* thisp, ...args*/) { + if (arguments.length > 1) { + var thisp = arguments[1]; + var args = array_slice(arguments, 2); + + var originalCallback = callback; + callback = function () { + var combinedArgs = args.concat(array_slice(arguments)); + return originalCallback.apply(thisp, combinedArgs); + }; + } + return function () { + var deferred = defer(); + var args = array_slice(arguments); + // add a continuation that resolves the promise + args.push(deferred.makeNodeResolver()); + // trap exceptions thrown by the callback + fapply(callback, args) + .fail(deferred.reject); + return deferred.promise; + }; +} + +/** + * Calls a method of a Node-style object that accepts a Node-style + * callback with a given array of arguments, plus a provided callback. + * @param object an object that has the named method + * @param {String} name name of the method of object + * @param {Array} args arguments to pass to the method; the callback + * will be provided by Q and appended to these arguments. + * @returns a promise for the value or error + */ +exports.npost = npost; +function npost(object, name, args) { + return napply(object[name], object, args); +} + +/** + * Calls a method of a Node-style object that accepts a Node-style + * callback, forwarding the given variadic arguments, plus a provided + * callback argument. + * @param object an object that has the named method + * @param {String} name name of the method of object + * @param ...args arguments to pass to the method; the callback will + * be provided by Q and appended to these arguments. + * @returns a promise for the value or error + */ +exports.ninvoke = ninvoke; +function ninvoke(object, name /*, ...args*/) { + var args = array_slice(arguments, 2); + return napply(object[name], object, args); +} + +defend(exports); + +});