www

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

commit 1575cfa84ac693002454afe960b66ee43214f096
parent e63364c7882e816fb35f85cce2f47146f5993afa
Author: Dan Stillman <dstillman@zotero.org>
Date:   Sat, 16 Feb 2013 22:43:39 -0500

Update Q library

Diffstat:
Mresource/q.js | 991++++++++++++++++++++++++++++++++++---------------------------------------------
1 file changed, 430 insertions(+), 561 deletions(-)

diff --git a/resource/q.js b/resource/q.js @@ -1,9 +1,4 @@ // 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, setImmediate: false, - ReturnValue: false, cajaVM: false, ses: false, bootstrap: false */ /*! * * Copyright 2009-2012 Kris Kowal under the terms of the MIT @@ -29,33 +24,6 @@ * 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. */ (function (definition) { @@ -73,7 +41,7 @@ // CommonJS } else if (typeof exports === "object") { - definition(void 0, exports); + module.exports = definition(); // RequireJS } else if (typeof define === "function") { @@ -84,12 +52,9 @@ if (!ses.ok()) { return; } else { - ses.makeQ = function () { - var Q = {}; - return definition(void 0, Q); - }; + ses.makeQ = definition; } - + // Mozilla JSM } else if (~String(this).indexOf('BackstagePass')) { EXPORTED_SYMBOLS = ["Q"]; @@ -136,14 +101,14 @@ _runningTimers.push(timer); } }; - definition(void 0, Q = {}); - + Q = definition(); + // <script> } else { - definition(void 0, Q = {}); + Q = definition(); } -})(function (require, exports) { +})(function () { "use strict"; // All code after this point will be filtered from stack traces reported @@ -153,19 +118,9 @@ var qFileName; // shims -// used for fallback "defend" and in "allResolved" +// used for fallback 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; @@ -174,28 +129,69 @@ if (typeof process !== "undefined") { nextTick = process.nextTick; } else if (typeof setImmediate === "function") { // In IE10, or use 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); - }; + if (typeof window !== "undefined") { + nextTick = setImmediate.bind(window); + } else { + nextTick = setImmediate; + } } else { - // old browsers - nextTick = function (task) { - setTimeout(task, 0); - }; + (function () { + // linked list of tasks (single, with head node) + var head = {task: void 0, next: null}, tail = head, + maxPendingTicks = 2, pendingTicks = 0, queuedTasks = 0, usedTicks = 0, + requestTick; + + function onTick() { + // In case of multiple tasks ensure at least one subsequent tick + // to handle remaining tasks in case one throws. + --pendingTicks; + + if (++usedTicks >= maxPendingTicks) { + // Amortize latency after thrown exceptions. + usedTicks = 0; + maxPendingTicks *= 4; // fast grow! + var expectedTicks = queuedTasks && Math.min(queuedTasks - 1, maxPendingTicks); + while (pendingTicks < expectedTicks) { + ++pendingTicks; + requestTick(); + } + } + + while (queuedTasks) { + --queuedTasks; // decrement here to ensure it's never negative + head = head.next; + var task = head.task; + head.task = void 0; + task(); + } + + usedTicks = 0; + } + + nextTick = function (task) { + tail = tail.next = {task: task, next: null}; + if (pendingTicks < ++queuedTasks && pendingTicks < maxPendingTicks) { + ++pendingTicks; + requestTick(); + } + }; + + if (typeof MessageChannel !== "undefined") { + // modern browsers + // http://www.nonblocking.io/2011/06/windownexttick.html + var channel = new MessageChannel(); + channel.port1.onmessage = onTick; + requestTick = function () { + channel.port2.postMessage(0); + }; + + } else { + // old browsers + requestTick = function () { + setTimeout(onTick, 0); + }; + } + })(); } // Attempt to make generics safe in the face of downstream @@ -209,19 +205,15 @@ if (typeof process !== "undefined") { // 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 () { - return f.call.apply(f, arguments); - }; +function uncurryThis(f) { + var call = Function.call; + return function () { + return call.apply(f, arguments); }; } +// This is equivalent, but slower: +// uncurryThis = Function_bind.bind(Function_bind.call); +// http://jsperf.com/uncurrythis var array_slice = uncurryThis(Array.prototype.slice); @@ -283,15 +275,19 @@ var object_create = Object.create || function (prototype) { return new Type(); }; +var object_hasOwnProperty = uncurryThis(Object.prototype.hasOwnProperty); + var object_keys = Object.keys || function (object) { var keys = []; for (var key in object) { - keys.push(key); + if (object_hasOwnProperty(object, key)) { + keys.push(key); + } } return keys; }; -var object_toString = Object.prototype.toString; +var object_toString = uncurryThis(Object.prototype.toString); // generator related shims @@ -313,133 +309,57 @@ if (typeof ReturnValue !== "undefined") { // 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); - } +Q.longStackJumpLimit = 1; + +var STACK_JUMP_SEPARATOR = "From previous event:"; + +function makeStackTraceLong(error, promise) { + // 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 ``done``ing. See #57. + if (promise.stack && + typeof error === "object" && + error !== null && + error.stack && + error.stack.indexOf(STACK_JUMP_SEPARATOR) === -1 + ) { + error.stack = filterStackString(error.stack) + + "\n" + STACK_JUMP_SEPARATOR + "\n" + + filterStackString(promise.stack); } - 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>"; +function filterStackString(stackString) { + var lines = stackString.split("\n"); + var desiredLines = []; + for (var i = 0; i < lines.length; ++i) { + var line = lines[i]; + + if (!isInternalFrame(line) && !isNodeFrame(line)) { + desiredLines.push(line); } - } else if (isConstructor) { - line += "new " + (functionName || "<anonymous>"); - } else if (functionName) { - line += functionName; - } else { - line += fileLocation; - addPrefix = false; - } - if (addPrefix) { - line += " (" + fileLocation + ")"; } - return line; + return desiredLines.join("\n"); } -function isInternalFrame(fileName, frame) { - if (fileName !== qFileName) { - return false; - } - var line = frame.getLineNumber(); - return line >= qStartingLine && line <= qEndingLine; +function isNodeFrame(stackLine) { + return stackLine.indexOf("(module.js:") !== -1 || + stackLine.indexOf("(node.js:") !== -1; } -/* - * 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" && - !isInternalFrame(fileName, frame) - ); - }); - }; +function isInternalFrame(stackLine) { + var pieces = /at .+ \((.*):(\d+):\d+\)/.exec(stackLine); - var stack = objectWithStack.stack; + if (!pieces) { + return false; + } - Error.prepareStackTrace = oldPrepareStackTrace; + var fileName = pieces[1]; + var lineNumber = pieces[2]; - return stack; + return fileName === qFileName && + lineNumber >= qStartingLine && + lineNumber <= qEndingLine; } // discover own file name and line number range for filtering stack @@ -479,10 +399,19 @@ function deprecate(callback, name, alternative) { // beginning of real work /** + * Creates fulfilled promises from non-promises, + * Passes Q promises through, + * Coerces CommonJS/Promises/A+ promises to Q promises. + */ +function Q(value) { + return resolve(value); +} + +/** * Performs a task in a future turn of the event loop. * @param {Function} task */ -exports.nextTick = nextTick; +Q.nextTick = nextTick; /** * Constructs a {promise, resolve} object. @@ -493,7 +422,7 @@ exports.nextTick = nextTick; * object. To put the promise in the same state as another promise, invoke the * resolver with that other promise. */ -exports.defer = defer; +Q.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 @@ -506,16 +435,16 @@ function defer() { var deferred = object_create(defer.prototype); var promise = object_create(makePromise.prototype); - promise.promiseSend = function (op, _, __, progress) { + promise.promiseDispatch = function (resolve, op, operands) { var args = array_slice(arguments); if (pending) { pending.push(args); - if (op === "when" && progress) { - progressListeners.push(progress); + if (op === "when" && operands[1]) { // progress operand + progressListeners.push(operands[1]); } } else { nextTick(function () { - value.promiseSend.apply(value, args); + value.promiseDispatch.apply(value, args); }); } }; @@ -524,11 +453,17 @@ function defer() { if (pending) { return promise; } - return value.valueOf(); + value = valueOf(value); // shorten chain + return value; }; - if (Error.captureStackTrace) { + if (Error.captureStackTrace && Q.longStackJumpLimit > 0) { Error.captureStackTrace(promise, defer); + + // Reify the stack into a string by using the accessor; this prevents + // memory leaks as per GH-111. At the same time, cut off the first line; + // it's always just "[object Promise]\n", as per the `toString`. + promise.stack = promise.stack.substring(promise.stack.indexOf("\n") + 1); } function become(resolvedValue) { @@ -538,28 +473,26 @@ function defer() { value = resolve(resolvedValue); array_reduce(pending, function (undefined, pending) { nextTick(function () { - value.promiseSend.apply(value, pending); + value.promiseDispatch.apply(value, pending); }); }, void 0); pending = void 0; progressListeners = void 0; - return value; } - defend(promise); - deferred.promise = promise; deferred.resolve = become; + deferred.fulfill = function (value) { + become(fulfill(value)); + }; deferred.reject = function (exception) { - return become(reject(exception)); + become(reject(exception)); }; - deferred.notify = function () { + deferred.notify = function (progress) { if (pending) { - var args = arguments; - array_reduce(progressListeners, function (undefined, progressListener) { nextTick(function () { - progressListener.apply(void 0, args); + progressListener(progress); }); }, void 0); } @@ -585,8 +518,6 @@ defer.prototype.makeNodeResolver = function () { } }; }; -// XXX deprecated -defer.prototype.node = deprecate(defer.prototype.makeNodeResolver, "node", "makeNodeResolver"); /** * @param makePromise {Function} a function that returns nothing and accepts @@ -594,7 +525,7 @@ defer.prototype.node = deprecate(defer.prototype.makeNodeResolver, "node", "make * @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; +Q.promise = promise; function promise(makePromise) { var deferred = defer(); fcall( @@ -617,8 +548,8 @@ function promise(makePromise) { * 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) { +Q.makePromise = makePromise; +function makePromise(descriptor, fallback, valueOf, exception, isException) { if (fallback === void 0) { fallback = function (op) { return reject(new Error("Promise does not support operation: " + op)); @@ -627,20 +558,19 @@ function makePromise(descriptor, fallback, valueOf, exception) { var promise = object_create(makePromise.prototype); - promise.promiseSend = function (op, resolved /* ...args */) { - var args = array_slice(arguments, 2); + promise.promiseDispatch = function (resolve, op, args) { var result; try { if (descriptor[op]) { result = descriptor[op].apply(promise, args); } else { - result = fallback.apply(promise, [op].concat(args)); + result = fallback.call(promise, op, args); } } catch (exception) { result = reject(exception); } - if (resolved) { - resolved(result); + if (resolve) { + resolve(result); } }; @@ -648,12 +578,10 @@ function makePromise(descriptor, fallback, valueOf, exception) { promise.valueOf = valueOf; } - if (exception) { + if (isException) { promise.exception = exception; } - defend(promise); - return promise; } @@ -669,25 +597,25 @@ makePromise.prototype.thenResolve = function (value) { // Chainable methods array_reduce( [ - "isResolved", "isFulfilled", "isRejected", - "when", "spread", "send", - "get", "put", "del", - "post", "invoke", + "isFulfilled", "isRejected", "isPending", + "dispatch", + "when", "spread", + "get", "put", "set", "del", "delete", + "post", "send", "invoke", "keys", - "apply", "call", "bind", "fapply", "fcall", "fbind", "all", "allResolved", - "view", "viewInfo", "timeout", "delay", - "catch", "finally", "fail", "fin", "progress", "end", "done", + "catch", "finally", "fail", "fin", "progress", "done", + "nfcall", "nfapply", "nfbind", "denodeify", "nbind", "ncall", "napply", "nbind", - "npost", "ninvoke", - "nend" + "npost", "nsend", "ninvoke", + "nodeify" ], function (undefined, name) { makePromise.prototype[name] = function () { - return exports[name].apply( - exports, + return Q[name].apply( + Q, [this].concat(array_slice(arguments)) ); }; @@ -703,8 +631,6 @@ 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. @@ -714,49 +640,50 @@ defend(makePromise.prototype); * @param object * @returns most resolved (nearest) form of the object */ -exports.nearer = valueOf; +Q.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 { + if (isPromise(value)) { return value.valueOf(); } + return value; } /** * @returns whether the given object is a promise. * Otherwise it is a fulfilled value. */ -exports.isPromise = isPromise; +Q.isPromise = isPromise; function isPromise(object) { - return object && typeof object.promiseSend === "function"; + return object && typeof object.promiseDispatch === "function"; +} + +Q.isPromiseAlike = isPromiseAlike; +function isPromiseAlike(object) { + return object && typeof object.then === "function"; } /** - * @returns whether the given object is a resolved promise. + * @returns whether the given object is a pending promise, meaning not + * fulfilled or rejected. */ -exports.isResolved = isResolved; -function isResolved(object) { - return isFulfilled(object) || isRejected(object); +Q.isPending = isPending; +function isPending(object) { + return !isFulfilled(object) && !isRejected(object); } /** * @returns whether the given object is a value or fulfilled * promise. */ -exports.isFulfilled = isFulfilled; +Q.isFulfilled = isFulfilled; function isFulfilled(object) { - return !isPromise(valueOf(object)); + return !isPromiseAlike(valueOf(object)); } /** * @returns whether the given object is a rejected promise. */ -exports.isRejected = isRejected; +Q.isRejected = isRejected; function isRejected(object) { object = valueOf(object); return isPromise(object) && 'exception' in object; @@ -781,13 +708,28 @@ function displayErrors() { errorsDisplayed = true; } +// Show unhandled rejection if Node exits without handling an outstanding +// rejection. (Note that Browserify presently produces a process global +// without the Emitter on interface) +if (typeof process !== "undefined" && process.on) { + process.on("exit", function () { + for (var i = 0; i < errors.length; i++) { + var error = errors[i]; + if (error && typeof error.stack !== "undefined") { + console.warn("Unhandled rejected promise:", error.stack); + } else { + console.warn("Unhandled rejected promise (no stack):", error); + } + } + }); +} + /** * Constructs a rejected promise. * @param exception value describing the failure */ -exports.reject = reject; +Q.reject = reject; function reject(exception) { - exception = exception || new Error(); var rejection = makePromise({ "when": function (rejected) { // note that the error has been handled @@ -798,13 +740,13 @@ function reject(exception) { rejections.splice(at, 1); } } - return rejected ? rejected(exception) : reject(exception); + return rejected ? rejected(exception) : this; } }, function fallback() { return reject(exception); }, function valueOf() { return this; - }, exception); + }, exception, true); // note that the error has not been handled displayErrors(); rejections.push(rejection); @@ -813,34 +755,11 @@ function reject(exception) { } /** - * Constructs a promise for an immediate reference. + * Constructs a fulfilled 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; - } - // In order to break infinite recursion or loops between `then` and - // `resolve`, it is necessary to attempt to extract fulfilled values - // out of foreign promise implementations before attempting to wrap - // them as unresolved promises. It is my hope that other - // implementations will implement `valueOf` to synchronously extract - // the fulfillment value from their fulfilled promises. If the - // other promise library does not implement `valueOf`, the - // implementations on primordial prototypes are harmless. - object = valueOf(object); - // assimilate thenables, CommonJS/Promises/A - if (object && typeof object.then === "function") { - var deferred = defer(); - object.then(deferred.resolve, deferred.reject, deferred.notify); - return deferred.promise; - } +Q.fulfill = fulfill; +function fulfill(object) { return makePromise({ "when": function () { return object; @@ -848,41 +767,23 @@ function resolve(object) { "get": function (name) { return object[name]; }, - "put": function (name, value) { + "set": function (name, value) { object[name] = value; - return object; }, - "del": function (name) { + "delete": function (name) { delete object[name]; - return object; - }, - "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); + "post": function (name, args) { + // Mark Miller proposes that post with no name should apply a + // promised function. + if (name == null) { // iff name is null or undefined + return object.apply(void 0, args); + } else { + return object[name].apply(object, args); } - return { - "type": typeof object, - "properties": properties - }; + }, + "apply": function (thisP, args) { + return object.apply(thisP, args); }, "keys": function () { return object_keys(object); @@ -893,6 +794,53 @@ function resolve(object) { } /** + * Constructs a promise for an immediate reference, passes promises through, or + * coerces promises from different systems. + * @param value immediate reference or promise + */ +Q.resolve = resolve; +function resolve(value) { + // 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(value)) { + return value; + } + // In order to break infinite recursion or loops between `then` and + // `resolve`, it is necessary to attempt to extract fulfilled values + // out of foreign promise implementations before attempting to wrap + // them as unresolved promises. It is my hope that other + // implementations will implement `valueOf` to synchronously extract + // the fulfillment value from their fulfilled promises. If the + // other promise library does not implement `valueOf`, the + // implementations on primordial prototypes are harmless. + value = valueOf(value); + // assimilate thenables, CommonJS/Promises/A+ + if (isPromiseAlike(value)) { + return coerce(value); + } else { + return fulfill(value); + } +} + +/** + * Converts thenables to Q promises. + * @param promise thenable promise + * @returns a Q promise + */ +function coerce(promise) { + var deferred = defer(); + nextTick(function () { + try { + promise.then(deferred.resolve, deferred.reject, deferred.notify); + } catch (exception) { + deferred.reject(exception); + } + }); + return deferred.promise; +} + +/** * Annotates an object such that it will never be * transferred away from this process over any promise * communication channel. @@ -901,60 +849,17 @@ function resolve(object) { * additionally responds to the "isDef" message * without a rejection. */ -exports.master = master; +Q.master = master; function master(object) { return makePromise({ "isDef": function () {} - }, function fallback() { - var args = array_slice(arguments); - return send.apply(void 0, [object].concat(args)); + }, function fallback(op, args) { + return dispatch(object, op, 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() { - 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. * @@ -971,7 +876,7 @@ function view(object) { * @param progressed function to be called on any progress notifications * @return promise for the return value from the invoked callback */ -exports.when = when; +Q.when = when; function when(value, fulfilled, rejected, progressed) { var deferred = defer(); var done = false; // ensure the untrusted promise makes at most a @@ -979,43 +884,66 @@ function when(value, fulfilled, rejected, progressed) { function _fulfilled(value) { try { - return fulfilled ? fulfilled(value) : value; + return typeof fulfilled === "function" ? fulfilled(value) : value; } catch (exception) { return reject(exception); } } function _rejected(exception) { - try { - return rejected ? rejected(exception) : reject(exception); - } catch (newException) { - return reject(newException); + if (typeof rejected === "function") { + makeStackTraceLong(exception, resolvedValue); + try { + return rejected(exception); + } catch (newException) { + return reject(newException); + } } + return reject(exception); + } + + function _progressed(value) { + return typeof progressed === "function" ? progressed(value) : value; } var resolvedValue = resolve(value); nextTick(function () { - resolvedValue.promiseSend("when", function (value) { + resolvedValue.promiseDispatch(function (value) { if (done) { return; } done = true; deferred.resolve(_fulfilled(value)); - }, function (exception) { + }, "when", [function (exception) { if (done) { return; } done = true; deferred.resolve(_rejected(exception)); - }); + }]); }); - // Progress listeners need to be attached in the current tick. - if (progressed) { - resolvedValue.promiseSend("when", void 0, void 0, progressed); - } + // Progress propagator need to be attached in the current tick. + resolvedValue.promiseDispatch(void 0, "when", [void 0, function (value) { + var newValue; + var threw = false; + try { + newValue = _progressed(value); + } catch (e) { + threw = true; + if (Q.onerror) { + Q.onerror(e); + } else { + throw e; + } + } + + if (!threw) { + deferred.notify(newValue); + } + }]); return deferred.promise; } @@ -1030,12 +958,12 @@ function when(value, fulfilled, rejected, progressed) { * @returns a promise for the return value or thrown exception of * either callback. */ -exports.spread = spread; +Q.spread = spread; function spread(promise, fulfilled, rejected) { return when(promise, function (valuesOrPromises) { return all(valuesOrPromises).then(function (values) { return fulfilled.apply(void 0, values); - }); + }, rejected); }, rejected); } @@ -1072,7 +1000,7 @@ function spread(promise, fulfilled, rejected) { * [1]: http://wiki.ecmascript.org/doku.php?id=strawman:async_functions#reference_implementation * */ -exports.async = async; +Q.async = async; function async(makeGenerator) { return function () { // when verb is "send", arg is a value @@ -1110,7 +1038,7 @@ function async(makeGenerator) { * Q.return(foo + bar); * }) */ -exports['return'] = _return; +Q['return'] = _return; function _return(value) { throw new QReturnValue(value); } @@ -1130,65 +1058,27 @@ function _return(value) { * @param {function} callback The function to decorate * @returns {function} a function that has been decorated. */ -exports.promised = promised; +Q.promised = promised; function promised(callback) { return function () { - return all([this, all(arguments)]).spread(function (self, args) { - return callback.apply(self, args); + return spread([this, all(arguments)], function (self, args) { + return callback.apply(self, args); }); }; } /** - * 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; +Q.dispatch = dispatch; function dispatch(object, op, args) { var deferred = defer(); - object = resolve(object); nextTick(function () { - object.promiseSend.apply( - object, - [op, deferred.resolve].concat(args) - ); + resolve(object).promiseDispatch(deferred.resolve, op, args); }); return deferred.promise; } @@ -1199,7 +1089,7 @@ function dispatch(object, op, args) { * * "dispatcher" constructs methods like "get(promise, name)" and "put(promise)". */ -exports.dispatcher = dispatcher; +Q.dispatcher = dispatcher; function dispatcher(op) { return function (object) { var args = array_slice(arguments, 1); @@ -1213,7 +1103,7 @@ function dispatcher(op) { * @param name name of property to get * @return promise for the property value */ -exports.get = dispatcher("get"); +Q.get = dispatcher("get"); /** * Sets the value of a property in a future turn. @@ -1222,7 +1112,7 @@ exports.get = dispatcher("get"); * @param value new value of property * @return promise for the return value */ -exports.put = dispatcher("put"); +Q.set = dispatcher("set"); /** * Deletes a property in a future turn. @@ -1230,8 +1120,8 @@ exports.put = dispatcher("put"); * @param name name of property to delete * @return promise for the return value */ -exports["delete"] = // XXX experimental -exports.del = dispatcher("del"); +Q["delete"] = // XXX experimental +Q.del = dispatcher("delete"); /** * Invokes a method in a future turn. @@ -1246,7 +1136,7 @@ exports.del = dispatcher("del"); * @return promise for the return value */ // bound locally because it is used by other methods -var post = exports.post = dispatcher("post"); +var post = Q.post = dispatcher("post"); /** * Invokes a method in a future turn. @@ -1255,38 +1145,21 @@ var post = exports.post = dispatcher("post"); * @param ...args array of invocation arguments * @return promise for the return value */ -exports.invoke = function (value, name) { +Q.send = send; +Q.invoke = send; // synonyms +function send(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); +Q.fapply = fapply; +function fapply(value, args) { + return dispatch(value, "apply", [void 0, args]); } /** @@ -1294,8 +1167,8 @@ function call(value, thisp) { * @param object promise or immediate reference for target function * @param ...args array of application arguments */ -exports["try"] = fcall; // XXX experimental -exports.fcall = fcall; +Q["try"] = fcall; // XXX experimental +Q.fcall = fcall; function fcall(value) { var args = array_slice(arguments, 1); return fapply(value, args); @@ -1305,30 +1178,14 @@ function fcall(value) { * 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; +Q.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); + return dispatch(value, "apply", [this, allArgs]); }; } @@ -1338,7 +1195,7 @@ function fbind(value) { * @param object promise or immediate reference for target object * @return promise for the keys of the eventually resolved object */ -exports.keys = dispatcher("keys"); +Q.keys = dispatcher("keys"); /** * Turns an array of promises into a promise for an array. If any of @@ -1349,7 +1206,7 @@ exports.keys = dispatcher("keys"); */ // By Mark Miller // http://wiki.ecmascript.org/doku.php?id=strawman:concurrency&rev=1308776521#allfulfilled -exports.all = all; +Q.all = all; function all(promises) { return when(promises, function (promises) { var countDown = promises.length; @@ -1386,13 +1243,14 @@ function all(promises) { * (or values) * @return a promise for an array of promises */ -exports.allResolved = allResolved; +Q.allResolved = allResolved; function allResolved(promises) { return when(promises, function (promises) { + promises = array_map(promises, resolve); return when(all(array_map(promises, function (promise) { return when(promise, noop, noop); })), function () { - return array_map(promises, resolve); + return promises; }); }); } @@ -1406,8 +1264,8 @@ function allResolved(promises) { * given promise is rejected * @returns a promise for the return value of the callback */ -exports["catch"] = // XXX experimental -exports.fail = fail; +Q["catch"] = // XXX experimental +Q.fail = fail; function fail(promise, rejected) { return when(promise, void 0, rejected); } @@ -1420,10 +1278,9 @@ function fail(promise, rejected) { * @param {Function} callback to receive any progress notifications * @returns the given promise, unchanged */ -exports.progress = progress; +Q.progress = progress; function progress(promise, progressed) { - when(promise, void 0, void 0, progressed); - return promise; + return when(promise, void 0, void 0, progressed); } /** @@ -1437,8 +1294,8 @@ function progress(promise, progressed) { * @returns a promise for the resolution of the given promise when * ``fin`` is done. */ -exports["finally"] = // XXX experimental -exports.fin = fin; +Q["finally"] = // XXX experimental +Q.fin = fin; function fin(promise, callback) { return when(promise, function (value) { return when(callback(), function () { @@ -1457,48 +1314,30 @@ function fin(promise, callback) { * @param {Any*} promise at the end of a chain of promises * @returns nothing */ -exports.end = deprecate(done, "end", "done"); // XXX deprecated, use done -exports.done = done; +Q.done = done; function done(promise, fulfilled, rejected, progress) { - function onUnhandledError(error) { + var onUnhandledError = 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 ``done``ing. See #57. - var errorStackFrames; - if ( - Error.captureStackTrace && - typeof error === "object" && - (errorStackFrames = getStackFrames(error)) - ) { - var promiseStackFrames = getStackFrames(promise); - - // Check to make sure the stack trace hasn't already been - // rendered (possibly by us). - if (typeof errorStackFrames !== "string") { - var combinedStackFrames = errorStackFrames.concat( - "From previous event:", - promiseStackFrames - ); - error.stack = formatStackTrace(error, combinedStackFrames); - } - } + makeStackTraceLong(error, promise); - if (exports.onerror) { - exports.onerror(error); + if (Q.onerror) { + Q.onerror(error); } else { throw error; } }); - } + }; // Avoid unnecessary `nextTick`ing via an unnecessary `when`. var promiseToHandle = fulfilled || rejected || progress ? when(promise, fulfilled, rejected, progress) : promise; + if (typeof process === "object" && process && process.domain) { + onUnhandledError = process.domain.bind(onUnhandledError); + } fail(promiseToHandle, onUnhandledError); } @@ -1510,7 +1349,7 @@ function done(promise, fulfilled, rejected, progress) { * @returns a promise for the resolution of the given promise if it is * fulfilled before the timeout, otherwise rejected. */ -exports.timeout = timeout; +Q.timeout = timeout; function timeout(promise, ms) { var deferred = defer(); var timeoutId = setTimeout(function () { @@ -1520,7 +1359,11 @@ function timeout(promise, ms) { when(promise, function (value) { clearTimeout(timeoutId); deferred.resolve(value); - }, deferred.reject); + }, function (exception) { + clearTimeout(timeoutId); + deferred.reject(exception); + }); + return deferred.promise; } @@ -1532,7 +1375,7 @@ function timeout(promise, ms) { * @returns a promise for the resolution of the given promise after some * time has elapsed. */ -exports.delay = delay; +Q.delay = delay; function delay(promise, timeout) { if (timeout === void 0) { timeout = promise; @@ -1546,65 +1389,80 @@ function delay(promise, timeout) { } /** - * 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. + * Passes a continuation to a Node function, which is called with the given + * arguments provided as an array, and returns a promise. * - * var FS = (require)("fs"); - * Q.napply(FS.readFile, FS, [__filename]) + * Q.nfapply(FS.readFile, [__filename]) * .then(function (content) { * }) * */ -exports.napply = napply; -function napply(callback, thisp, args) { - return nbind(callback, thisp).apply(void 0, args); +Q.nfapply = nfapply; +function nfapply(callback, args) { + var nodeArgs = array_slice(args); + var deferred = defer(); + nodeArgs.push(deferred.makeNodeResolver()); + + fapply(callback, nodeArgs).fail(deferred.reject); + return deferred.promise; } /** - * Passes a continuation to a Node function, which is called with a given - * `this` value and arguments provided individually, and returns a promise. + * Passes a continuation to a Node function, which is called with the given + * arguments provided individually, and returns a promise. * - * var FS = (require)("fs"); - * Q.ncall(FS.readFile, FS, __filename) + * Q.nfcall(FS.readFile, __filename) * .then(function (content) { * }) * */ -exports.ncall = ncall; -function ncall(callback, thisp /*, ...args*/) { - var args = array_slice(arguments, 2); - return napply(callback, thisp, args); +Q.nfcall = nfcall; +function nfcall(callback/*, ...args */) { + var nodeArgs = array_slice(arguments, 1); + var deferred = defer(); + nodeArgs.push(deferred.makeNodeResolver()); + + fapply(callback, nodeArgs).fail(deferred.reject); + return deferred.promise; } /** * Wraps a NodeJS continuation passing function and returns an equivalent * version that returns a promise. * - * Q.nbind(FS.readFile, FS)(__filename) + * Q.nfbind(FS.readFile, __filename)("utf-8") * .then(console.log) * .done() * */ -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); - }; - } +Q.nfbind = nfbind; +Q.denodeify = Q.nfbind; // synonyms +function nfbind(callback/*, ...args */) { + var baseArgs = array_slice(arguments, 1); return function () { + var nodeArgs = baseArgs.concat(array_slice(arguments)); 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); + nodeArgs.push(deferred.makeNodeResolver()); + + fapply(callback, nodeArgs).fail(deferred.reject); + return deferred.promise; + }; +} + +Q.nbind = nbind; +function nbind(callback/*, ... args*/) { + var baseArgs = array_slice(arguments, 1); + return function () { + var nodeArgs = baseArgs.concat(array_slice(arguments)); + var deferred = defer(); + nodeArgs.push(deferred.makeNodeResolver()); + + var thisArg = this; + function bound() { + return callback.apply(thisArg, arguments); + } + + fapply(bound, nodeArgs).fail(deferred.reject); return deferred.promise; }; } @@ -1618,9 +1476,14 @@ function nbind(callback /* thisp, ...args*/) { * will be provided by Q and appended to these arguments. * @returns a promise for the value or error */ -exports.npost = npost; +Q.npost = npost; function npost(object, name, args) { - return napply(object[name], object, args); + var nodeArgs = array_slice(args || []); + var deferred = defer(); + nodeArgs.push(deferred.makeNodeResolver()); + + post(object, name, nodeArgs).fail(deferred.reject); + return deferred.promise; } /** @@ -1633,14 +1496,18 @@ function npost(object, name, args) { * 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); +Q.nsend = nsend; +Q.ninvoke = Q.nsend; // synonyms +function nsend(object, name /*, ...args*/) { + var nodeArgs = array_slice(arguments, 2); + var deferred = defer(); + nodeArgs.push(deferred.makeNodeResolver()); + post(object, name, nodeArgs).fail(deferred.reject); + return deferred.promise; } -exports.nend = nend; -function nend(promise, nodeback) { +Q.nodeify = nodeify; +function nodeify(promise, nodeback) { if (nodeback) { promise.then(function (value) { nextTick(function () { @@ -1659,4 +1526,6 @@ function nend(promise, nodeback) { // All code before this point will be filtered from stack traces. var qEndingLine = captureLine(); +return Q; + });