commit 2d7d72fb2ab0f9b68196fe9990d9f5c6209db660
parent 81e94b4475b587b75a2ccede68bb274abf837df8
Author: Dan Stillman <dstillman@zotero.org>
Date: Fri, 16 Aug 2013 18:15:00 -0400
Upgrade Q, and change allResolved() to allSettled()
Not fully tested
Diffstat:
4 files changed, 1093 insertions(+), 659 deletions(-)
diff --git a/chrome/content/zotero/xpcom/storage.js b/chrome/content/zotero/xpcom/storage.js
@@ -149,7 +149,7 @@ Zotero.Sync.Storage = new function () {
else {
var promise = mode.cacheCredentials();
}
- promises.push(Q.allResolved([mode, promise]));
+ promises.push(Q.allSettled([mode, promise]));
}
}
@@ -161,12 +161,12 @@ Zotero.Sync.Storage = new function () {
// Mark WebDAV verification failure as user library error.
// We ignore credentials-caching errors for ZFS and let the
// later requests fail.
- cacheCredentialsPromises.forEach(function (promise) {
- let mode = promise[0].valueOf();
+ cacheCredentialsPromises.forEach(function (results) {
+ let mode = results[0].value;
if (mode == Zotero.Sync.Storage.WebDAV) {
- if (promise[1].isRejected()) {
- promises.push(Q.allResolved(
- [0, promise[1]]
+ if (results[1].state == "rejected") {
+ promises.push(Q.allSettled(
+ [0, Q.reject(results[1].reason)]
));
// Skip further syncing of user library
delete libraryModes[0];
@@ -179,15 +179,13 @@ Zotero.Sync.Storage = new function () {
// Get the last sync time for each library
if (self.downloadOnSync(libraryID)) {
- promises.push(Q.allResolved(
+ promises.push(Q.allSettled(
[libraryID, libraryModes[libraryID].getLastSyncTime(libraryID)]
));
}
// If download-as-needed, we don't need the last sync time
else {
- promises.push(Q.allResolved(
- [libraryID, null]
- ));
+ promises.push(Q.allSettled([libraryID, null]));
}
}
return Q.all(promises);
@@ -202,20 +200,17 @@ Zotero.Sync.Storage = new function () {
var libraryQueues = [];
// Get the libraries we have sync times for
- promises.forEach(function (promise) {
- let libraryID = promise[0].valueOf();
- let lastSyncTime = promise[1].valueOf();
- if (promise[1].isFulfilled()) {
+ promises.forEach(function (results) {
+ let libraryID = results[0].value;
+ let lastSyncTime = results[1].value;
+ if (results[1].state == "fulfilled") {
librarySyncTimes[libraryID] = lastSyncTime;
}
else {
- let e = lastSyncTime.exception;
- Zotero.debug(e);
- Components.utils.reportError(e);
+ Zotero.debug(lastSyncTime.reason);
+ Components.utils.reportError(lastSyncTime.reason);
// Pass rejected promise through
- libraryQueues.push(Q.allResolved(
- [libraryID, lastSyncTime]
- ));
+ libraryQueues.push(results);
}
});
@@ -316,7 +311,7 @@ Zotero.Sync.Storage = new function () {
// Start queues for each library
for (let libraryID in librarySyncTimes) {
libraryID = parseInt(libraryID);
- libraryQueues.push(Q.allResolved(
+ libraryQueues.push(Q.allSettled(
[libraryID, Zotero.Sync.Storage.QueueManager.start(libraryID)]
));
}
@@ -331,20 +326,20 @@ Zotero.Sync.Storage = new function () {
var changedLibraries = [];
var finalPromises = [];
- promises.forEach(function (promise) {
- var libraryID = promise[0].valueOf();
- var libraryQueues = promise[1].valueOf();
+ promises.forEach(function (results) {
+ var libraryID = results[0].value;
+ var libraryQueues = results[1].value;
- if (promise[1].isFulfilled()) {
+ if (results[1].state == "fulfilled") {
libraryQueues.forEach(function (queuePromise) {
- let result = queuePromise.valueOf();
if (queuePromise.isFulfilled()) {
+ let result = queuePromise.inspect().value;
Zotero.debug("File " + result.type + " sync finished "
+ "for library " + libraryID);
if (result.localChanges) {
changedLibraries.push(libraryID);
}
- finalPromises.push(Q.allResolved([
+ finalPromises.push(Q.allSettled([
libraryID,
libraryModes[libraryID].setLastSyncTime(
libraryID,
@@ -353,10 +348,12 @@ Zotero.Sync.Storage = new function () {
]));
}
else {
- result = result.exception;
- Zotero.debug("File " + result.type + " sync failed "
+ let e = queuePromise.inspect().reason;
+ Zotero.debug("File " + e.type + " sync failed "
+ "for library " + libraryID);
- finalPromises.push([libraryID, queuePromise]);
+ finalPromises.push(Q.allSettled(
+ [libraryID, Q.reject(e)]
+ ));
}
});
}
@@ -389,27 +386,26 @@ Zotero.Sync.Storage = new function () {
}
return Q.all(finalPromises)
- .then(function (promises) {
- var results = {
- changesMade: !!changedLibraries.length,
- errors: []
- };
-
- promises.forEach(function (promise) {
- var libraryID = promise[0].valueOf();
- if (promise[1].isRejected()) {
- var result = promise[1].valueOf();
- result = result.exception;
- if (typeof result == 'string') {
- result = new Error(result);
- }
- result.libraryID = libraryID;
- results.errors.push(result);
+ .then(function (promises) {
+ var results = {
+ changesMade: !!changedLibraries.length,
+ errors: []
+ };
+
+ promises.forEach(function (promiseResults) {
+ var libraryID = promiseResults[0].value;
+ if (promiseResults[1].state == "rejected") {
+ let e = promiseResults[1].reason;
+ if (typeof e == 'string') {
+ e = new Error(e);
}
- });
-
- return results;
+ e.libraryID = libraryID;
+ results.errors.push(e);
+ }
});
+
+ return results;
+ });
});
}
diff --git a/chrome/content/zotero/xpcom/storage/queueManager.js b/chrome/content/zotero/xpcom/storage/queueManager.js
@@ -53,26 +53,26 @@ Zotero.Sync.Storage.QueueManager = new function () {
Zotero.debug("No files to sync" + suffix);
}
- return Q.allResolved(promises)
- .then(function (promises) {
- Zotero.debug("All storage queues are finished" + suffix);
-
- promises.forEach(function (promise) {
- // Check for conflicts to resolve
- if (promise.isFulfilled()) {
- var result = promise.valueOf();
- if (result.conflicts.length) {
- Zotero.debug("Reconciling conflicts for library " + result.libraryID);
- Zotero.debug(result.conflicts);
- var data = _reconcileConflicts(result.conflicts);
- if (data) {
- _processMergeData(data);
- }
+ return Q.allSettled(promises)
+ .then(function (results) {
+ Zotero.debug("All storage queues are finished" + suffix);
+
+ results.forEach(function (result) {
+ // Check for conflicts to resolve
+ if (result.state == "fulfilled") {
+ result = result.value;
+ if (result.conflicts.length) {
+ Zotero.debug("Reconciling conflicts for library " + result.libraryID);
+ Zotero.debug(result.conflicts);
+ var data = _reconcileConflicts(result.conflicts);
+ if (data) {
+ _processMergeData(data);
}
}
- });
- return promises;
+ }
});
+ return promises;
+ });
};
this.stop = function (libraryID) {
diff --git a/resource/concurrent-caller.js b/resource/concurrent-caller.js
@@ -26,7 +26,7 @@
EXPORTED_SYMBOLS = ["ConcurrentCaller"];
Components.utils.import("resource://zotero/q.js");
-/**
+f/**
* Call a fixed number of functions at once, queueing the rest until slots
* open and returning a promise for the final completion. The functions do
* not need to return promises, but they should if they have asynchronous
@@ -79,7 +79,7 @@ ConcurrentCaller.prototype.fcall = function (func) {
//this._log("Running fcall on function");
promises.push(this.fcall(func[i]));
}
- return this.stopOnError ? Q.all(promises) : Q.allResolved(promises);
+ return this.stopOnError ? Q.all(promises) : Q.allSettled(promises);
}
// If we're at the maximum number of concurrent functions,
diff --git a/resource/q.js b/resource/q.js
@@ -28,7 +28,7 @@
(function (definition) {
// Turn off strict mode for this function so we can assign to global.Q
- /*jshint strict: false*/
+ /* jshint strict: false */
// This file will function properly as a <script> tag, or a module
// using CommonJS and NodeJS or RequireJS module formats. In
@@ -44,7 +44,7 @@
module.exports = definition();
// RequireJS
- } else if (typeof define === "function") {
+ } else if (typeof define === "function" && define.amd) {
define(definition);
// SES (Secure EcmaScript)
@@ -55,7 +55,7 @@
ses.makeQ = definition;
}
- // Mozilla JSM
+ // Mozilla JSM (added by Zotero)
} else if (~String(this).indexOf('BackstagePass')) {
EXPORTED_SYMBOLS = ["Q"];
@@ -70,9 +70,6 @@
timer = Components.classes["@mozilla.org/timer;1"].
createInstance(Components.interfaces.nsITimer);
timer.initWithCallback({"notify":function() {
- // XXX Remove when we drop support for Fx <24
- if(useMethodjit !== undefined) Components.utils.methodjit = useMethodjit;
-
// Remove timer from array so it can be garbage collected
_runningTimers.splice(_runningTimers.indexOf(timer), 1);
@@ -112,6 +109,13 @@
})(function () {
"use strict";
+var hasStacks = false;
+try {
+ throw new Error();
+} catch (e) {
+ hasStacks = !!e.stack;
+}
+
// All code after this point will be filtered from stack traces reported
// by Q.
var qStartingLine = captureLine();
@@ -122,78 +126,118 @@ var qFileName;
// used for fallback in "allResolved"
var noop = function () {};
-// use the fastest possible means to execute a task in a future turn
+// 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 setImmediate === "function") {
- // In IE10, or use https://github.com/NobleJS/setImmediate
- if (typeof window !== "undefined") {
- nextTick = setImmediate.bind(window);
- } else {
- nextTick = setImmediate;
- }
-} else {
- (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();
- }
+var nextTick =(function () {
+ // linked list of tasks (single, with head node)
+ var head = {task: void 0, next: null};
+ var tail = head;
+ var flushing = false;
+ var requestTick = void 0;
+ var isNodeJS = false;
+
+ function flush() {
+ /* jshint loopfunc: true */
+
+ while (head.next) {
+ head = head.next;
+ var task = head.task;
+ head.task = void 0;
+ var domain = head.domain;
+
+ if (domain) {
+ head.domain = void 0;
+ domain.enter();
}
- while (queuedTasks) {
- --queuedTasks; // decrement here to ensure it's never negative
- head = head.next;
- var task = head.task;
- head.task = void 0;
+ try {
task();
+
+ } catch (e) {
+ if (isNodeJS) {
+ // In node, uncaught exceptions are considered fatal errors.
+ // Re-throw them synchronously to interrupt flushing!
+
+ // Ensure continuation if the uncaught exception is suppressed
+ // listening "uncaughtException" events (as domains does).
+ // Continue in next event to avoid tick recursion.
+ if (domain) {
+ domain.exit();
+ }
+ setTimeout(flush, 0);
+ if (domain) {
+ domain.enter();
+ }
+
+ throw e;
+
+ } else {
+ // In browsers, uncaught exceptions are not fatal.
+ // Re-throw them asynchronously to avoid slow-downs.
+ setTimeout(function() {
+ throw e;
+ }, 0);
+ }
}
- usedTicks = 0;
+ if (domain) {
+ domain.exit();
+ }
}
- nextTick = function (task) {
- tail = tail.next = {task: task, next: null};
- if (pendingTicks < ++queuedTasks && pendingTicks < maxPendingTicks) {
- ++pendingTicks;
- requestTick();
- }
+ flushing = false;
+ }
+
+ nextTick = function (task) {
+ tail = tail.next = {
+ task: task,
+ domain: isNodeJS && process.domain,
+ next: null
};
- 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);
- };
+ if (!flushing) {
+ flushing = true;
+ requestTick();
+ }
+ };
+ if (typeof process !== "undefined" && process.nextTick) {
+ // Node.js before 0.9. Note that some fake-Node environments, like the
+ // Mocha test runner, introduce a `process` global without a `nextTick`.
+ isNodeJS = true;
+
+ requestTick = function () {
+ process.nextTick(flush);
+ };
+
+ } else if (typeof setImmediate === "function") {
+ // In IE10, Node.js 0.9+, or https://github.com/NobleJS/setImmediate
+ if (typeof window !== "undefined") {
+ requestTick = setImmediate.bind(window, flush);
} else {
- // old browsers
requestTick = function () {
- setTimeout(onTick, 0);
+ setImmediate(flush);
};
}
- })();
-}
+
+ } else if (typeof MessageChannel !== "undefined") {
+ // modern browsers
+ // http://www.nonblocking.io/2011/06/windownexttick.html
+ var channel = new MessageChannel();
+ channel.port1.onmessage = flush;
+ requestTick = function () {
+ channel.port2.postMessage(0);
+ };
+
+ } else {
+ // old browsers
+ requestTick = function () {
+ setTimeout(flush, 0);
+ };
+ }
+
+ return nextTick;
+})();
// Attempt to make generics safe in the face of downstream
// modifications.
@@ -206,8 +250,8 @@ 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 call = Function.call;
function uncurryThis(f) {
- var call = Function.call;
return function () {
return call.apply(f, arguments);
};
@@ -290,8 +334,13 @@ var object_keys = Object.keys || function (object) {
var object_toString = uncurryThis(Object.prototype.toString);
+function isObject(value) {
+ return value === Object(value);
+}
+
// generator related shims
+// FIXME: Remove this function once ES6 generators are in SpiderMonkey.
function isStopIteration(exception) {
return (
object_toString(exception) === "[object StopIteration]" ||
@@ -299,6 +348,8 @@ function isStopIteration(exception) {
);
}
+// FIXME: Remove this helper and Q.return once ES6 generators are in
+// SpiderMonkey.
var QReturnValue;
if (typeof ReturnValue !== "undefined") {
QReturnValue = ReturnValue;
@@ -308,25 +359,46 @@ if (typeof ReturnValue !== "undefined") {
};
}
-// long stack traces
+// Until V8 3.19 / Chromium 29 is released, SpiderMonkey is the only
+// engine that has a deployed base of browsers that support generators.
+// However, SM's generators use the Python-inspired semantics of
+// outdated ES6 drafts. We would like to support ES6, but we'd also
+// like to make it possible to use generators in deployed browsers, so
+// we also support Python-style generators. At some point we can remove
+// this block.
+var hasES6Generators;
+try {
+ /* jshint evil: true, nonew: false */
+ new Function("(function* (){ yield 1; })");
+ hasES6Generators = true;
+} catch (e) {
+ hasES6Generators = false;
+}
-Q.longStackJumpLimit = 1;
+// long stack traces
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 &&
+ // If possible, transform the error stack trace by removing Node and Q
+ // cruft, then concatenating with the stack trace of `promise`. See #57.
+ if (hasStacks &&
+ 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);
+ var stacks = [];
+ for (var p = promise; !!p; p = p.source) {
+ if (p.stack) {
+ stacks.unshift(p.stack);
+ }
+ }
+ stacks.unshift(error.stack);
+
+ var concatedStacks = stacks.join("\n" + STACK_JUMP_SEPARATOR + "\n");
+ error.stack = filterStackString(concatedStacks);
}
}
@@ -336,7 +408,7 @@ function filterStackString(stackString) {
for (var i = 0; i < lines.length; ++i) {
var line = lines[i];
- if (!isInternalFrame(line) && !isNodeFrame(line)) {
+ if (!isInternalFrame(line) && !isNodeFrame(line) && line) {
desiredLines.push(line);
}
}
@@ -348,15 +420,36 @@ function isNodeFrame(stackLine) {
stackLine.indexOf("(node.js:") !== -1;
}
+function getFileNameAndLineNumber(stackLine) {
+ // Named functions: "at functionName (filename:lineNumber:columnNumber)"
+ // In IE10 function name can have spaces ("Anonymous function") O_o
+ var attempt1 = /at .+ \((.+):(\d+):(?:\d+)\)$/.exec(stackLine);
+ if (attempt1) {
+ return [attempt1[1], Number(attempt1[2])];
+ }
+
+ // Anonymous functions: "at filename:lineNumber:columnNumber"
+ var attempt2 = /at ([^ ]+):(\d+):(?:\d+)$/.exec(stackLine);
+ if (attempt2) {
+ return [attempt2[1], Number(attempt2[2])];
+ }
+
+ // Firefox style: "function@filename:lineNumber or @filename:lineNumber"
+ var attempt3 = /.*@(.+):(\d+)$/.exec(stackLine);
+ if (attempt3) {
+ return [attempt3[1], Number(attempt3[2])];
+ }
+}
+
function isInternalFrame(stackLine) {
- var pieces = /at .+ \((.*):(\d+):\d+\)/.exec(stackLine);
+ var fileNameAndLineNumber = getFileNameAndLineNumber(stackLine);
- if (!pieces) {
+ if (!fileNameAndLineNumber) {
return false;
}
- var fileName = pieces[1];
- var lineNumber = pieces[2];
+ var fileName = fileNameAndLineNumber[0];
+ var lineNumber = fileNameAndLineNumber[1];
return fileName === qFileName &&
lineNumber >= qStartingLine &&
@@ -366,31 +459,31 @@ function isInternalFrame(stackLine) {
// discover own file name and line number range for filtering stack
// traces
function captureLine() {
- if (Error.captureStackTrace) {
- var fileName, lineNumber;
-
- var oldPrepareStackTrace = Error.prepareStackTrace;
-
- Error.prepareStackTrace = function (error, frames) {
- fileName = frames[1].getFileName();
- lineNumber = frames[1].getLineNumber();
- };
+ if (!hasStacks) {
+ return;
+ }
- // teases call of temporary prepareStackTrace
- // JSHint and Closure Compiler generate known warnings here
- /*jshint expr: true */
- new Error().stack;
+ try {
+ throw new Error();
+ } catch (e) {
+ var lines = e.stack.split("\n");
+ var firstLine = lines[0].indexOf("@") > 0 ? lines[1] : lines[2];
+ var fileNameAndLineNumber = getFileNameAndLineNumber(firstLine);
+ if (!fileNameAndLineNumber) {
+ return;
+ }
- Error.prepareStackTrace = oldPrepareStackTrace;
- qFileName = fileName;
- return lineNumber;
+ qFileName = fileNameAndLineNumber[0];
+ return fileNameAndLineNumber[1];
}
}
function deprecate(callback, name, alternative) {
return function () {
- if (typeof console !== "undefined" && typeof console.warn === "function") {
- console.warn(name + " is deprecated, use " + alternative + " instead.", new Error("").stack);
+ if (typeof console !== "undefined" &&
+ typeof console.warn === "function") {
+ console.warn(name + " is deprecated, use " + alternative +
+ " instead.", new Error("").stack);
}
return callback.apply(callback, arguments);
};
@@ -400,13 +493,26 @@ 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.
+ * Constructs a promise for an immediate reference, passes promises through, or
+ * coerces promises from different systems.
+ * @param value immediate reference or promise
*/
function Q(value) {
- return 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;
+ }
+
+ // assimilate thenables
+ if (isPromiseAlike(value)) {
+ return coerce(value);
+ } else {
+ return fulfill(value);
+ }
}
+Q.resolve = Q;
/**
* Performs a task in a future turn of the event loop.
@@ -415,88 +521,131 @@ function Q(value) {
Q.nextTick = nextTick;
/**
- * Constructs a {promise, resolve} object.
+ * Controls whether or not long stack traces will be on
+ */
+Q.longStackSupport = false;
+
+/**
+ * Constructs a {promise, resolve, reject} 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.
+ * `resolve` is a callback to invoke with a more resolved value for the
+ * promise. To fulfill the promise, invoke `resolve` with any value that is
+ * not a thenable. To reject the promise, invoke `resolve` with a rejected
+ * thenable, or invoke `reject` with the reason directly. To resolve the
+ * promise to another thenable, thus putting it in the same state, invoke
+ * `resolve` with that other thenable.
*/
Q.defer = defer;
function defer() {
- // if "pending" is an "Array", that indicates that the promise has not yet
+ // if "messages" 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
+ // element of the messages 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 = [], progressListeners = [], value;
+ // promise using the `resolve` function because it handles both fully
+ // non-thenable values and other thenables gracefully.
+ var messages = [], progressListeners = [], resolvedPromise;
var deferred = object_create(defer.prototype);
- var promise = object_create(makePromise.prototype);
+ var promise = object_create(Promise.prototype);
promise.promiseDispatch = function (resolve, op, operands) {
var args = array_slice(arguments);
- if (pending) {
- pending.push(args);
+ if (messages) {
+ messages.push(args);
if (op === "when" && operands[1]) { // progress operand
progressListeners.push(operands[1]);
}
} else {
nextTick(function () {
- value.promiseDispatch.apply(value, args);
+ resolvedPromise.promiseDispatch.apply(resolvedPromise, args);
});
}
};
- promise.valueOf = function () {
- if (pending) {
+ // XXX deprecated
+ promise.valueOf = deprecate(function () {
+ if (messages) {
return promise;
}
- value = valueOf(value); // shorten chain
- return value;
- };
+ var nearerValue = nearer(resolvedPromise);
+ if (isPromise(nearerValue)) {
+ resolvedPromise = nearerValue; // shorten chain
+ }
+ return nearerValue;
+ }, "valueOf", "inspect");
- if (Error.captureStackTrace && Q.longStackJumpLimit > 0) {
- Error.captureStackTrace(promise, defer);
+ promise.inspect = function () {
+ if (!resolvedPromise) {
+ return { state: "pending" };
+ }
+ return resolvedPromise.inspect();
+ };
- // 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);
+ if (Q.longStackSupport && hasStacks) {
+ try {
+ throw new Error();
+ } catch (e) {
+ // NOTE: don't try to use `Error.captureStackTrace` or transfer the
+ // accessor around; that causes memory leaks as per GH-111. Just
+ // reify the stack trace as a string ASAP.
+ //
+ // At the same time, cut off the first line; it's always just
+ // "[object Promise]\n", as per the `toString`.
+ promise.stack = e.stack.substring(e.stack.indexOf("\n") + 1);
+ }
}
- function become(resolvedValue) {
- if (!pending) {
- return;
- }
- value = resolve(resolvedValue);
- array_reduce(pending, function (undefined, pending) {
+ // NOTE: we do the checks for `resolvedPromise` in each method, instead of
+ // consolidating them into `become`, since otherwise we'd create new
+ // promises with the lines `become(whatever(value))`. See e.g. GH-252.
+
+ function become(newPromise) {
+ resolvedPromise = newPromise;
+ promise.source = newPromise;
+
+ array_reduce(messages, function (undefined, message) {
nextTick(function () {
- value.promiseDispatch.apply(value, pending);
+ newPromise.promiseDispatch.apply(newPromise, message);
});
}, void 0);
- pending = void 0;
+
+ messages = void 0;
progressListeners = void 0;
}
deferred.promise = promise;
- deferred.resolve = become;
+ deferred.resolve = function (value) {
+ if (resolvedPromise) {
+ return;
+ }
+
+ become(Q(value));
+ };
+
deferred.fulfill = function (value) {
+ if (resolvedPromise) {
+ return;
+ }
+
become(fulfill(value));
};
- deferred.reject = function (exception) {
- become(reject(exception));
+ deferred.reject = function (reason) {
+ if (resolvedPromise) {
+ return;
+ }
+
+ become(reject(reason));
};
deferred.notify = function (progress) {
- if (pending) {
- array_reduce(progressListeners, function (undefined, progressListener) {
- nextTick(function () {
- progressListener(progress);
- });
- }, void 0);
+ if (resolvedPromise) {
+ return;
}
+
+ array_reduce(progressListeners, function (undefined, progressListener) {
+ nextTick(function () {
+ progressListener(progress);
+ });
+ }, void 0);
};
return deferred;
@@ -521,27 +670,91 @@ defer.prototype.makeNodeResolver = function () {
};
/**
- * @param makePromise {Function} a function that returns nothing and accepts
+ * @param resolver {Function} a function that returns nothing and accepts
* the resolve, reject, and notify 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
+ * functions, or rejected by a thrown exception in resolver
*/
Q.promise = promise;
-function promise(makePromise) {
+function promise(resolver) {
+ if (typeof resolver !== "function") {
+ throw new TypeError("resolver must be a function.");
+ }
var deferred = defer();
- fcall(
- makePromise,
- deferred.resolve,
- deferred.reject,
- deferred.notify
- ).fail(deferred.reject);
+ try {
+ resolver(deferred.resolve, deferred.reject, deferred.notify);
+ } catch (reason) {
+ deferred.reject(reason);
+ }
return deferred.promise;
}
+// XXX experimental. This method is a way to denote that a local value is
+// serializable and should be immediately dispatched to a remote upon request,
+// instead of passing a reference.
+Q.passByCopy = function (object) {
+ //freeze(object);
+ //passByCopies.set(object, true);
+ return object;
+};
+
+Promise.prototype.passByCopy = function () {
+ //freeze(object);
+ //passByCopies.set(object, true);
+ return this;
+};
+
+/**
+ * If two promises eventually fulfill to the same value, promises that value,
+ * but otherwise rejects.
+ * @param x {Any*}
+ * @param y {Any*}
+ * @returns {Any*} a promise for x and y if they are the same, but a rejection
+ * otherwise.
+ *
+ */
+Q.join = function (x, y) {
+ return Q(x).join(y);
+};
+
+Promise.prototype.join = function (that) {
+ return Q([this, that]).spread(function (x, y) {
+ if (x === y) {
+ // TODO: "===" should be Object.is or equiv
+ return x;
+ } else {
+ throw new Error("Can't join: not the same: " + x + " " + y);
+ }
+ });
+};
+
+/**
+ * Returns a promise for the first of an array of promises to become fulfilled.
+ * @param answers {Array[Any*]} promises to race
+ * @returns {Any*} the first promise to be fulfilled
+ */
+Q.race = race;
+function race(answerPs) {
+ return promise(function(resolve, reject) {
+ // Switch to this once we can assume at least ES5
+ // answerPs.forEach(function(answerP) {
+ // Q(answerP).then(resolve, reject);
+ // });
+ // Use this in the meantime
+ for (var i = 0, len = answerPs.length; i < len; i++) {
+ Q(answerPs[i]).then(resolve, reject);
+ }
+ });
+}
+
+Promise.prototype.race = function () {
+ return this.then(Q.race);
+};
+
/**
* 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
+ * set(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
@@ -549,15 +762,22 @@ function promise(makePromise) {
* of the returned object, apart from that it is usable whereever promises are
* bought and sold.
*/
-Q.makePromise = makePromise;
-function makePromise(descriptor, fallback, valueOf, exception, isException) {
+Q.makePromise = Promise;
+function Promise(descriptor, fallback, inspect) {
if (fallback === void 0) {
fallback = function (op) {
- return reject(new Error("Promise does not support operation: " + op));
+ return reject(new Error(
+ "Promise does not support operation: " + op
+ ));
+ };
+ }
+ if (inspect === void 0) {
+ inspect = function () {
+ return {state: "unknown"};
};
}
- var promise = object_create(makePromise.prototype);
+ var promise = object_create(Promise.prototype);
promise.promiseDispatch = function (resolve, op, args) {
var result;
@@ -575,63 +795,138 @@ function makePromise(descriptor, fallback, valueOf, exception, isException) {
}
};
- if (valueOf) {
- promise.valueOf = valueOf;
- }
+ promise.inspect = inspect;
+
+ // XXX deprecated `valueOf` and `exception` support
+ if (inspect) {
+ var inspected = inspect();
+ if (inspected.state === "rejected") {
+ promise.exception = inspected.reason;
+ }
- if (isException) {
- promise.exception = exception;
+ promise.valueOf = deprecate(function () {
+ var inspected = inspect();
+ if (inspected.state === "pending" ||
+ inspected.state === "rejected") {
+ return promise;
+ }
+ return inspected.value;
+ });
}
return promise;
}
-// provide thenables, CommonJS/Promises/A
-makePromise.prototype.then = function (fulfilled, rejected, progressed) {
- return when(this, fulfilled, rejected, progressed);
-};
-
-makePromise.prototype.thenResolve = function (value) {
- return when(this, function () { return value; });
-};
-
-// Chainable methods
-array_reduce(
- [
- "isFulfilled", "isRejected", "isPending",
- "dispatch",
- "when", "spread",
- "get", "put", "set", "del", "delete",
- // .send() disabled by Zotero for Mozilla Task.jsm compatibility
- //"post", "send", "invoke",
- "post", "invoke",
- "keys",
- "fapply", "fcall", "fbind",
- "all", "allResolved",
- "timeout", "delay",
- "catch", "finally", "fail", "fin", "progress", "done",
- "nfcall", "nfapply", "nfbind", "denodeify", "nbind",
- "ncall", "napply", "nbind",
- "npost", "nsend", "ninvoke",
- "nodeify"
- ],
- function (undefined, name) {
- makePromise.prototype[name] = function () {
- return Q[name].apply(
- Q,
- [this].concat(array_slice(arguments))
- );
- };
- },
- void 0
-);
+Promise.prototype.toString = function () {
+ return "[object Promise]";
+};
+
+Promise.prototype.then = function (fulfilled, rejected, progressed) {
+ var self = this;
+ 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 typeof fulfilled === "function" ? fulfilled(value) : value;
+ } catch (exception) {
+ return reject(exception);
+ }
+ }
+
+ function _rejected(exception) {
+ if (typeof rejected === "function") {
+ makeStackTraceLong(exception, self);
+ try {
+ return rejected(exception);
+ } catch (newException) {
+ return reject(newException);
+ }
+ }
+ return reject(exception);
+ }
+
+ function _progressed(value) {
+ return typeof progressed === "function" ? progressed(value) : value;
+ }
+
+ nextTick(function () {
+ self.promiseDispatch(function (value) {
+ if (done) {
+ return;
+ }
+ done = true;
+
+ deferred.resolve(_fulfilled(value));
+ }, "when", [function (exception) {
+ if (done) {
+ return;
+ }
+ done = true;
+
+ deferred.resolve(_rejected(exception));
+ }]);
+ });
+
+ // Progress propagator need to be attached in the current tick.
+ self.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);
+ }
+ }]);
-makePromise.prototype.toSource = function () {
- return this.toString();
+ return deferred.promise;
};
-makePromise.prototype.toString = function () {
- return "[object Promise]";
+/**
+ * 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
+ * @param progressed function to be called on any progress notifications
+ * @return promise for the return value from the invoked callback
+ */
+Q.when = when;
+function when(value, fulfilled, rejected, progressed) {
+ return Q(value).then(fulfilled, rejected, progressed);
+}
+
+Promise.prototype.thenResolve = function (value) {
+ return this.then(function () { return value; });
+};
+
+Q.thenResolve = function (promise, value) {
+ return Q(promise).thenResolve(value);
+};
+
+Promise.prototype.thenReject = function (reason) {
+ return this.then(function () { throw reason; });
+};
+
+Q.thenReject = function (promise, reason) {
+ return Q(promise).thenReject(reason);
};
/**
@@ -643,10 +938,15 @@ makePromise.prototype.toString = function () {
* @param object
* @returns most resolved (nearest) form of the object
*/
-Q.nearer = valueOf;
-function valueOf(value) {
+
+// XXX should we re-do this?
+Q.nearer = nearer;
+function nearer(value) {
if (isPromise(value)) {
- return value.valueOf();
+ var inspected = value.inspect();
+ if (inspected.state === "fulfilled") {
+ return inspected.value;
+ }
}
return value;
}
@@ -657,12 +957,14 @@ function valueOf(value) {
*/
Q.isPromise = isPromise;
function isPromise(object) {
- return object && typeof object.promiseDispatch === "function";
+ return isObject(object) &&
+ typeof object.promiseDispatch === "function" &&
+ typeof object.inspect === "function";
}
Q.isPromiseAlike = isPromiseAlike;
function isPromiseAlike(object) {
- return object && typeof object.then === "function";
+ return isObject(object) && typeof object.then === "function";
}
/**
@@ -671,89 +973,154 @@ function isPromiseAlike(object) {
*/
Q.isPending = isPending;
function isPending(object) {
- return !isFulfilled(object) && !isRejected(object);
+ return isPromise(object) && object.inspect().state === "pending";
}
+Promise.prototype.isPending = function () {
+ return this.inspect().state === "pending";
+};
+
/**
* @returns whether the given object is a value or fulfilled
* promise.
*/
Q.isFulfilled = isFulfilled;
function isFulfilled(object) {
- return !isPromiseAlike(valueOf(object));
+ return !isPromise(object) || object.inspect().state === "fulfilled";
}
+Promise.prototype.isFulfilled = function () {
+ return this.inspect().state === "fulfilled";
+};
+
/**
* @returns whether the given object is a rejected promise.
*/
Q.isRejected = isRejected;
function isRejected(object) {
- object = valueOf(object);
- return isPromise(object) && 'exception' in object;
+ return isPromise(object) && object.inspect().state === "rejected";
}
-var rejections = [];
-var errors = [];
-var errorsDisplayed;
-function displayErrors() {
+Promise.prototype.isRejected = function () {
+ return this.inspect().state === "rejected";
+};
+
+//// BEGIN UNHANDLED REJECTION TRACKING
+
+// This promise library consumes exceptions thrown in handlers so they can be
+// handled by a subsequent promise. The exceptions get added to this array when
+// they are created, and removed when they are handled. Note that in ES6 or
+// shimmed environments, this would naturally be a `Set`.
+var unhandledReasons = [];
+var unhandledRejections = [];
+var unhandledReasonsDisplayed = false;
+var trackUnhandledRejections = true;
+function displayUnhandledReasons() {
if (
- !errorsDisplayed &&
+ !unhandledReasonsDisplayed &&
typeof window !== "undefined" &&
!window.Touch &&
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);
+ console.warn("[Q] Unhandled rejection reasons (should be empty):",
+ unhandledReasons);
}
- errorsDisplayed = true;
+
+ unhandledReasonsDisplayed = 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);
- }
+function logUnhandledReasons() {
+ for (var i = 0; i < unhandledReasons.length; i++) {
+ var reason = unhandledReasons[i];
+ if (reason && typeof reason.stack !== "undefined") {
+ console.warn("Unhandled rejection reason:", reason.stack);
+ } else {
+ console.warn("Unhandled rejection reason (no stack):", reason);
}
- });
+ }
}
-/**
- * Constructs a rejected promise.
- * @param exception value describing the failure
- */
-Q.reject = reject;
-function reject(exception) {
- 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) : this;
+function resetUnhandledRejections() {
+ unhandledReasons.length = 0;
+ unhandledRejections.length = 0;
+ unhandledReasonsDisplayed = false;
+
+ if (!trackUnhandledRejections) {
+ trackUnhandledRejections = true;
+
+ // Show unhandled rejection reasons if Node exits without handling an
+ // outstanding rejection. (Note that Browserify presently produces a
+ // `process` global without the `EventEmitter` `on` method.)
+ if (typeof process !== "undefined" && process.on) {
+ process.on("exit", logUnhandledReasons);
}
- }, function fallback() {
- return reject(exception);
- }, function valueOf() {
+ }
+}
+
+function trackRejection(promise, reason) {
+ if (!trackUnhandledRejections) {
+ return;
+ }
+
+ unhandledRejections.push(promise);
+ unhandledReasons.push(reason);
+ displayUnhandledReasons();
+}
+
+function untrackRejection(promise) {
+ if (!trackUnhandledRejections) {
+ return;
+ }
+
+ var at = array_indexOf(unhandledRejections, promise);
+ if (at !== -1) {
+ unhandledRejections.splice(at, 1);
+ unhandledReasons.splice(at, 1);
+ }
+}
+
+Q.resetUnhandledRejections = resetUnhandledRejections;
+
+Q.getUnhandledReasons = function () {
+ // Make a copy so that consumers can't interfere with our internal state.
+ return unhandledReasons.slice();
+};
+
+Q.stopUnhandledRejectionTracking = function () {
+ resetUnhandledRejections();
+ if (typeof process !== "undefined" && process.on) {
+ process.removeListener("exit", logUnhandledReasons);
+ }
+ trackUnhandledRejections = false;
+};
+
+resetUnhandledRejections();
+
+//// END UNHANDLED REJECTION TRACKING
+
+/**
+ * Constructs a rejected promise.
+ * @param reason value describing the failure
+ */
+Q.reject = reject;
+function reject(reason) {
+ var rejection = Promise({
+ "when": function (rejected) {
+ // note that the error has been handled
+ if (rejected) {
+ untrackRejection(this);
+ }
+ return rejected ? rejected(reason) : this;
+ }
+ }, function fallback() {
return this;
- }, exception, true);
- // note that the error has not been handled
- displayErrors();
- rejections.push(rejection);
- errors.push(exception);
+ }, function inspect() {
+ return { state: "rejected", reason: reason };
+ });
+
+ // Note that the reason has not been handled.
+ trackRejection(rejection, reason);
+
return rejection;
}
@@ -762,71 +1129,41 @@ function reject(exception) {
* @param value immediate reference
*/
Q.fulfill = fulfill;
-function fulfill(object) {
- return makePromise({
+function fulfill(value) {
+ return Promise({
"when": function () {
- return object;
+ return value;
},
"get": function (name) {
- return object[name];
+ return value[name];
},
- "set": function (name, value) {
- object[name] = value;
+ "set": function (name, rhs) {
+ value[name] = rhs;
},
"delete": function (name) {
- delete object[name];
+ delete value[name];
},
"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);
+ if (name === null || name === void 0) {
+ return value.apply(void 0, args);
} else {
- return object[name].apply(object, args);
+ return value[name].apply(value, args);
}
},
- "apply": function (thisP, args) {
- return object.apply(thisP, args);
+ "apply": function (thisp, args) {
+ return value.apply(thisp, args);
},
"keys": function () {
- return object_keys(object);
+ return object_keys(value);
}
- }, void 0, function valueOf() {
- return object;
+ }, void 0, function inspect() {
+ return { state: "fulfilled", value: value };
});
}
/**
- * 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
@@ -854,101 +1191,13 @@ function coerce(promise) {
*/
Q.master = master;
function master(object) {
- return makePromise({
+ return Promise({
"isDef": function () {}
}, function fallback(op, args) {
return dispatch(object, op, args);
}, function () {
- return valueOf(object);
- });
-}
-
-/**
- * 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
- * @param progressed function to be called on any progress notifications
- * @return promise for the return value from the invoked callback
- */
-Q.when = when;
-function when(value, fulfilled, rejected, progressed) {
- 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 typeof fulfilled === "function" ? fulfilled(value) : value;
- } catch (exception) {
- return reject(exception);
- }
- }
-
- function _rejected(exception) {
- 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.promiseDispatch(function (value) {
- if (done) {
- return;
- }
- done = true;
-
- deferred.resolve(_fulfilled(value));
- }, "when", [function (exception) {
- if (done) {
- return;
- }
- done = true;
-
- deferred.resolve(_rejected(exception));
- }]);
+ return Q(object).inspect();
});
-
- // 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;
}
/**
@@ -962,20 +1211,27 @@ function when(value, fulfilled, rejected, progressed) {
* either callback.
*/
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);
+function spread(value, fulfilled, rejected) {
+ return Q(value).spread(fulfilled, rejected);
}
+Promise.prototype.spread = function (fulfilled, rejected) {
+ return this.all().then(function (array) {
+ return fulfilled.apply(void 0, array);
+ }, 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.
+ * them into asynchronous generators. Although generators are only part
+ * of the newest ECMAScript 6 drafts, 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.
+ *
+ * ES6 generators are currently part of V8 version 3.19 with the
+ * --harmony-generators runtime flag enabled. SpiderMonkey has had them
+ * for longer, but under an older Python-inspired form. This function
+ * works on both kinds of generators.
*
* Decorates a generator function such that:
* - it may yield promises
@@ -990,18 +1246,6 @@ function spread(promise, fulfilled, rejected) {
* 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
- *
*/
Q.async = async;
function async(makeGenerator) {
@@ -1010,45 +1254,83 @@ function async(makeGenerator) {
// 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 {
+ if (hasES6Generators) {
+ try {
+ result = generator[verb](arg);
+ } catch (exception) {
return reject(exception);
}
+ if (result.done) {
+ return result.value;
+ } else {
+ return when(result.value, callback, errback);
+ }
+ } else {
+ // FIXME: Remove this case when SM does ES6 generators.
+ try {
+ result = generator[verb](arg);
+ } catch (exception) {
+ if (isStopIteration(exception)) {
+ return exception.value;
+ } else {
+ return reject(exception);
+ }
+ }
+ return when(result, callback, errback);
}
- return when(result, callback, errback);
}
var generator = makeGenerator.apply(this, arguments);
- var callback = continuer.bind(continuer, "send");
+ var callback = continuer.bind(continuer, "next");
var errback = continuer.bind(continuer, "throw");
return callback();
};
}
/**
+ * The spawn function is a small wrapper around async that immediately
+ * calls the generator and also ends the promise chain, so that any
+ * unhandled errors are thrown instead of forwarded to the error
+ * handler. This is useful because it's extremely common to run
+ * generators at the top-level to work with libraries.
+ */
+Q.spawn = spawn;
+function spawn(makeGenerator) {
+ Q.done(Q.async(makeGenerator)());
+}
+
+// FIXME: Remove this interface once ES6 generators are in SpiderMonkey.
+/**
* Throws a ReturnValue exception to stop an asynchronous generator.
- * Only useful presently in Firefox/SpiderMonkey since generators are
- * implemented.
+ *
+ * This interface is a stop-gap measure to support generator return
+ * values in older Firefox/SpiderMonkey. In browsers that support ES6
+ * generators like Chromium 29, just use "return" in your generator
+ * functions.
+ *
* @param value the return value for the surrounding generator
* @throws ReturnValue exception with the value.
* @example
+ * // ES6 style
+ * Q.async(function* () {
+ * var foo = yield getFooPromise();
+ * var bar = yield getBarPromise();
+ * return foo + bar;
+ * })
+ * // Older SpiderMonkey style
* Q.async(function () {
* var foo = yield getFooPromise();
* var bar = yield getBarPromise();
* Q.return(foo + bar);
* })
*/
-Q['return'] = _return;
+Q["return"] = _return;
function _return(value) {
throw new QReturnValue(value);
}
/**
* The promised function decorator ensures that any promise arguments
- * are resolved and passed as values (`this` is also resolved and passed
+ * are settled and passed as values (`this` is also settled and passed
* as a value). It will also ensure that the result of a function is
* always a promise.
*
@@ -1056,7 +1338,7 @@ function _return(value) {
* var add = Q.promised(function (a, b) {
* return a + b;
* });
- * add(Q.resolve(a), Q.resolve(B));
+ * add(Q(a), Q(B));
*
* @param {function} callback The function to decorate
* @returns {function} a function that has been decorated.
@@ -1079,26 +1361,17 @@ function promised(callback) {
*/
Q.dispatch = dispatch;
function dispatch(object, op, args) {
+ return Q(object).dispatch(op, args);
+}
+
+Promise.prototype.dispatch = function (op, args) {
+ var self = this;
var deferred = defer();
nextTick(function () {
- resolve(object).promiseDispatch(deferred.resolve, op, args);
+ self.promiseDispatch(deferred.resolve, op, 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)".
- */
-Q.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.
@@ -1106,7 +1379,13 @@ function dispatcher(op) {
* @param name name of property to get
* @return promise for the property value
*/
-Q.get = dispatcher("get");
+Q.get = function (object, key) {
+ return Q(object).dispatch("get", [key]);
+};
+
+Promise.prototype.get = function (key) {
+ return this.dispatch("get", [key]);
+};
/**
* Sets the value of a property in a future turn.
@@ -1115,7 +1394,13 @@ Q.get = dispatcher("get");
* @param value new value of property
* @return promise for the return value
*/
-Q.set = dispatcher("set");
+Q.set = function (object, key, value) {
+ return Q(object).dispatch("set", [key, value]);
+};
+
+Promise.prototype.set = function (key, value) {
+ return this.dispatch("set", [key, value]);
+};
/**
* Deletes a property in a future turn.
@@ -1123,8 +1408,15 @@ Q.set = dispatcher("set");
* @param name name of property to delete
* @return promise for the return value
*/
-Q["delete"] = // XXX experimental
-Q.del = dispatcher("delete");
+Q.del = // XXX legacy
+Q["delete"] = function (object, key) {
+ return Q(object).dispatch("delete", [key]);
+};
+
+Promise.prototype.del = // XXX legacy
+Promise.prototype["delete"] = function (key) {
+ return this.dispatch("delete", [key]);
+};
/**
* Invokes a method in a future turn.
@@ -1139,7 +1431,15 @@ Q.del = dispatcher("delete");
* @return promise for the return value
*/
// bound locally because it is used by other methods
-var post = Q.post = dispatcher("post");
+Q.mapply = // XXX As proposed by "Redsandro"
+Q.post = function (object, name, args) {
+ return Q(object).dispatch("post", [name, args]);
+};
+
+Promise.prototype.mapply = // XXX As proposed by "Redsandro"
+Promise.prototype.post = function (name, args) {
+ return this.dispatch("post", [name, args]);
+};
/**
* Invokes a method in a future turn.
@@ -1148,35 +1448,46 @@ var post = Q.post = dispatcher("post");
* @param ...args array of invocation arguments
* @return promise for the return value
*/
-// Disabled by Zotero for Mozilla Task.jsm compatibility
-//Q.send = send;
-Q.invoke = send; // synonyms
-function send(value, name) {
- var args = array_slice(arguments, 2);
- return post(value, name, args);
-}
+// .send() disabled by Zotero for Mozilla Task.jsm compatibility
+//Q.send = // XXX Mark Miller's proposed parlance
+Q.mcall = // XXX As proposed by "Redsandro"
+Q.invoke = function (object, name /*...args*/) {
+ return Q(object).dispatch("post", [name, array_slice(arguments, 2)]);
+};
+
+// .send() disabled by Zotero for Mozilla Task.jsm compatibility
+//Promise.prototype.send = // XXX Mark Miller's proposed parlance
+Promise.prototype.mcall = // XXX As proposed by "Redsandro"
+Promise.prototype.invoke = function (name /*...args*/) {
+ return this.dispatch("post", [name, array_slice(arguments, 1)]);
+};
/**
* Applies the promised function in a future turn.
* @param object promise or immediate reference for target function
* @param args array of application arguments
*/
-Q.fapply = fapply;
-function fapply(value, args) {
- return dispatch(value, "apply", [void 0, args]);
-}
+Q.fapply = function (object, args) {
+ return Q(object).dispatch("apply", [void 0, args]);
+};
+
+Promise.prototype.fapply = function (args) {
+ return this.dispatch("apply", [void 0, args]);
+};
/**
* Calls the promised function in a future turn.
* @param object promise or immediate reference for target function
* @param ...args array of application arguments
*/
-Q["try"] = fcall; // XXX experimental
-Q.fcall = fcall;
-function fcall(value) {
- var args = array_slice(arguments, 1);
- return fapply(value, args);
-}
+Q["try"] =
+Q.fcall = function (object /* ...args*/) {
+ return Q(object).dispatch("apply", [void 0, array_slice(arguments, 1)]);
+};
+
+Promise.prototype.fcall = function (/*...args*/) {
+ return this.dispatch("apply", [void 0, array_slice(arguments)]);
+};
/**
* Binds the promised function, transforming return values into a fulfilled
@@ -1184,22 +1495,40 @@ function fcall(value) {
* @param object promise or immediate reference for target function
* @param ...args array of application arguments
*/
-Q.fbind = fbind;
-function fbind(value) {
+Q.fbind = function (object /*...args*/) {
+ var promise = Q(object);
var args = array_slice(arguments, 1);
return function fbound() {
- var allArgs = args.concat(array_slice(arguments));
- return dispatch(value, "apply", [this, allArgs]);
+ return promise.dispatch("apply", [
+ this,
+ args.concat(array_slice(arguments))
+ ]);
};
-}
+};
+Promise.prototype.fbind = function (/*...args*/) {
+ var promise = this;
+ var args = array_slice(arguments);
+ return function fbound() {
+ return promise.dispatch("apply", [
+ this,
+ args.concat(array_slice(arguments))
+ ]);
+ };
+};
/**
* 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
+ * @return promise for the keys of the eventually settled object
*/
-Q.keys = dispatcher("keys");
+Q.keys = function (object) {
+ return Q(object).dispatch("keys", []);
+};
+
+Promise.prototype.keys = function () {
+ return this.dispatch("keys", []);
+};
/**
* Turns an array of promises into a promise for an array. If any of
@@ -1213,33 +1542,45 @@ Q.keys = dispatcher("keys");
Q.all = all;
function all(promises) {
return when(promises, function (promises) {
- var countDown = promises.length;
- if (countDown === 0) {
- return resolve(promises);
- }
+ var countDown = 0;
var deferred = defer();
array_reduce(promises, function (undefined, promise, index) {
- if (isFulfilled(promise)) {
- promises[index] = valueOf(promise);
- if (--countDown === 0) {
- deferred.resolve(promises);
- }
+ var snapshot;
+ if (
+ isPromise(promise) &&
+ (snapshot = promise.inspect()).state === "fulfilled"
+ ) {
+ promises[index] = snapshot.value;
} else {
- when(promise, function (value) {
- promises[index] = value;
- if (--countDown === 0) {
- deferred.resolve(promises);
+ ++countDown;
+ when(
+ promise,
+ function (value) {
+ promises[index] = value;
+ if (--countDown === 0) {
+ deferred.resolve(promises);
+ }
+ },
+ deferred.reject,
+ function (progress) {
+ deferred.notify({ index: index, value: progress });
}
- })
- .fail(deferred.reject);
+ );
}
}, void 0);
+ if (countDown === 0) {
+ deferred.resolve(promises);
+ }
return deferred.promise;
});
}
+Promise.prototype.all = function () {
+ return all(this);
+};
+
/**
- * Waits for all promises to be resolved, either fulfilled or
+ * Waits for all promises to be settled, 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.
@@ -1247,10 +1588,10 @@ function all(promises) {
* (or values)
* @return a promise for an array of promises
*/
-Q.allResolved = allResolved;
+Q.allResolved = deprecate(allResolved, "allResolved", "allSettled");
function allResolved(promises) {
return when(promises, function (promises) {
- promises = array_map(promises, resolve);
+ promises = array_map(promises, Q);
return when(all(array_map(promises, function (promise) {
return when(promise, noop, noop);
})), function () {
@@ -1259,6 +1600,37 @@ function allResolved(promises) {
});
}
+Promise.prototype.allResolved = function () {
+ return allResolved(this);
+};
+
+/**
+ * @see Promise#allSettled
+ */
+Q.allSettled = allSettled;
+function allSettled(promises) {
+ return Q(promises).allSettled();
+}
+
+/**
+ * Turns an array of promises into a promise for an array of their states (as
+ * returned by `inspect`) when they have all settled.
+ * @param {Array[Any*]} values an array (or promise for an array) of values (or
+ * promises for values)
+ * @returns {Array[State]} an array of states for the respective values.
+ */
+Promise.prototype.allSettled = function () {
+ return this.then(function (promises) {
+ return all(array_map(promises, function (promise) {
+ promise = Q(promise);
+ function regardless() {
+ return promise.inspect();
+ }
+ return promise.then(regardless, regardless);
+ }));
+ });
+};
+
/**
* Captures the failure of a promise, giving an oportunity to recover
* with a callback. If the given promise is fulfilled, the returned
@@ -1268,11 +1640,15 @@ function allResolved(promises) {
* given promise is rejected
* @returns a promise for the return value of the callback
*/
-Q["catch"] = // XXX experimental
-Q.fail = fail;
-function fail(promise, rejected) {
- return when(promise, void 0, rejected);
-}
+Q.fail = // XXX legacy
+Q["catch"] = function (object, rejected) {
+ return Q(object).then(void 0, rejected);
+};
+
+Promise.prototype.fail = // XXX legacy
+Promise.prototype["catch"] = function (rejected) {
+ return this.then(void 0, rejected);
+};
/**
* Attaches a listener that can respond to progress notifications from a
@@ -1283,12 +1659,16 @@ function fail(promise, rejected) {
* @returns the given promise, unchanged
*/
Q.progress = progress;
-function progress(promise, progressed) {
- return when(promise, void 0, void 0, progressed);
+function progress(object, progressed) {
+ return Q(object).then(void 0, void 0, progressed);
}
+Promise.prototype.progress = function (progressed) {
+ return this.then(void 0, void 0, progressed);
+};
+
/**
- * Provides an opportunity to observe the rejection of a promise,
+ * Provides an opportunity to observe the settling 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.
@@ -1298,19 +1678,25 @@ function progress(promise, progressed) {
* @returns a promise for the resolution of the given promise when
* ``fin`` is done.
*/
-Q["finally"] = // XXX experimental
-Q.fin = fin;
-function fin(promise, callback) {
- return when(promise, function (value) {
- return when(callback(), function () {
+Q.fin = // XXX legacy
+Q["finally"] = function (object, callback) {
+ return Q(object)["finally"](callback);
+};
+
+Promise.prototype.fin = // XXX legacy
+Promise.prototype["finally"] = function (callback) {
+ callback = Q(callback);
+ return this.then(function (value) {
+ return callback.fcall().then(function () {
return value;
});
- }, function (exception) {
- return when(callback(), function () {
- return reject(exception);
+ }, function (reason) {
+ // TODO attempt to recycle the rejection with "this".
+ return callback.fcall().then(function () {
+ throw reason;
});
});
-}
+};
/**
* Terminates a chain of promises, forcing rejections to be
@@ -1318,14 +1704,16 @@ function fin(promise, callback) {
* @param {Any*} promise at the end of a chain of promises
* @returns nothing
*/
-Q.done = done;
-function done(promise, fulfilled, rejected, progress) {
+Q.done = function (object, fulfilled, rejected, progress) {
+ return Q(object).done(fulfilled, rejected, progress);
+};
+
+Promise.prototype.done = function (fulfilled, rejected, progress) {
var onUnhandledError = function (error) {
// forward to a future turn so that ``when``
// does not catch it and turn it into a rejection.
nextTick(function () {
makeStackTraceLong(error, promise);
-
if (Q.onerror) {
Q.onerror(error);
} else {
@@ -1335,62 +1723,73 @@ function done(promise, fulfilled, rejected, progress) {
};
// Avoid unnecessary `nextTick`ing via an unnecessary `when`.
- var promiseToHandle = fulfilled || rejected || progress ?
- when(promise, fulfilled, rejected, progress) :
- promise;
+ var promise = fulfilled || rejected || progress ?
+ this.then(fulfilled, rejected, progress) :
+ this;
if (typeof process === "object" && process && process.domain) {
onUnhandledError = process.domain.bind(onUnhandledError);
}
- fail(promiseToHandle, onUnhandledError);
-}
+
+ promise.then(void 0, onUnhandledError);
+};
/**
* Causes a promise to be rejected if it does not get fulfilled before
* some milliseconds time out.
* @param {Any*} promise
* @param {Number} milliseconds timeout
+ * @param {String} custom error message (optional)
* @returns a promise for the resolution of the given promise if it is
* fulfilled before the timeout, otherwise rejected.
*/
-Q.timeout = timeout;
-function timeout(promise, ms) {
+Q.timeout = function (object, ms, message) {
+ return Q(object).timeout(ms, message);
+};
+
+Promise.prototype.timeout = function (ms, message) {
var deferred = defer();
var timeoutId = setTimeout(function () {
- deferred.reject(new Error("Timed out after " + ms + " ms"));
+ deferred.reject(new Error(message || "Timed out after " + ms + " ms"));
}, ms);
- when(promise, function (value) {
+ this.then(function (value) {
clearTimeout(timeoutId);
deferred.resolve(value);
}, function (exception) {
clearTimeout(timeoutId);
deferred.reject(exception);
- });
+ }, deferred.notify);
return deferred.promise;
-}
+};
/**
- * Returns a promise for the given value (or promised value) after some
- * milliseconds.
+ * Returns a promise for the given value (or promised value), some
+ * milliseconds after it resolved. Passes rejections immediately.
* @param {Any*} promise
* @param {Number} milliseconds
- * @returns a promise for the resolution of the given promise after some
- * time has elapsed.
+ * @returns a promise for the resolution of the given promise after milliseconds
+ * time has elapsed since the resolution of the given promise.
+ * If the given promise rejects, that is passed immediately.
*/
-Q.delay = delay;
-function delay(promise, timeout) {
+Q.delay = function (object, timeout) {
if (timeout === void 0) {
- timeout = promise;
- promise = void 0;
+ timeout = object;
+ object = void 0;
}
- var deferred = defer();
- setTimeout(function () {
- deferred.resolve(promise);
- }, timeout);
- return deferred.promise;
-}
+ return Q(object).delay(timeout);
+};
+
+Promise.prototype.delay = function (timeout) {
+ return this.then(function (value) {
+ var deferred = defer();
+ setTimeout(function () {
+ deferred.resolve(value);
+ }, timeout);
+ return deferred.promise;
+ });
+};
/**
* Passes a continuation to a Node function, which is called with the given
@@ -1401,75 +1800,86 @@ function delay(promise, timeout) {
* })
*
*/
-Q.nfapply = nfapply;
-function nfapply(callback, args) {
- var nodeArgs = array_slice(args);
+Q.nfapply = function (callback, args) {
+ return Q(callback).nfapply(args);
+};
+
+Promise.prototype.nfapply = function (args) {
var deferred = defer();
+ var nodeArgs = array_slice(args);
nodeArgs.push(deferred.makeNodeResolver());
-
- fapply(callback, nodeArgs).fail(deferred.reject);
+ this.fapply(nodeArgs).fail(deferred.reject);
return deferred.promise;
-}
+};
/**
* Passes a continuation to a Node function, which is called with the given
* arguments provided individually, and returns a promise.
- *
- * Q.nfcall(FS.readFile, __filename)
- * .then(function (content) {
- * })
+ * @example
+ * Q.nfcall(FS.readFile, __filename)
+ * .then(function (content) {
+ * })
*
*/
-Q.nfcall = nfcall;
-function nfcall(callback/*, ...args */) {
- var nodeArgs = array_slice(arguments, 1);
+Q.nfcall = function (callback /*...args*/) {
+ var args = array_slice(arguments, 1);
+ return Q(callback).nfapply(args);
+};
+
+Promise.prototype.nfcall = function (/*...args*/) {
+ var nodeArgs = array_slice(arguments);
var deferred = defer();
nodeArgs.push(deferred.makeNodeResolver());
-
- fapply(callback, nodeArgs).fail(deferred.reject);
+ this.fapply(nodeArgs).fail(deferred.reject);
return deferred.promise;
-}
+};
/**
* Wraps a NodeJS continuation passing function and returns an equivalent
* version that returns a promise.
- *
- * Q.nfbind(FS.readFile, __filename)("utf-8")
- * .then(console.log)
- * .done()
- *
+ * @example
+ * Q.nfbind(FS.readFile, __filename)("utf-8")
+ * .then(console.log)
+ * .done()
*/
-Q.nfbind = nfbind;
-Q.denodeify = Q.nfbind; // synonyms
-function nfbind(callback/*, ...args */) {
+Q.nfbind =
+Q.denodeify = function (callback /*...args*/) {
var baseArgs = array_slice(arguments, 1);
return function () {
var nodeArgs = baseArgs.concat(array_slice(arguments));
var deferred = defer();
nodeArgs.push(deferred.makeNodeResolver());
-
- fapply(callback, nodeArgs).fail(deferred.reject);
+ Q(callback).fapply(nodeArgs).fail(deferred.reject);
return deferred.promise;
};
-}
+};
-Q.nbind = nbind;
-function nbind(callback/*, ... args*/) {
- var baseArgs = array_slice(arguments, 1);
+Promise.prototype.nfbind =
+Promise.prototype.denodeify = function (/*...args*/) {
+ var args = array_slice(arguments);
+ args.unshift(this);
+ return Q.denodeify.apply(void 0, args);
+};
+
+Q.nbind = function (callback, thisp /*...args*/) {
+ var baseArgs = array_slice(arguments, 2);
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);
+ return callback.apply(thisp, arguments);
}
-
- fapply(bound, nodeArgs).fail(deferred.reject);
+ Q(bound).fapply(nodeArgs).fail(deferred.reject);
return deferred.promise;
};
-}
+};
+
+Promise.prototype.nbind = function (/*thisp, ...args*/) {
+ var args = array_slice(arguments, 0);
+ args.unshift(this);
+ return Q.nbind.apply(void 0, args);
+};
/**
* Calls a method of a Node-style object that accepts a Node-style
@@ -1480,15 +1890,19 @@ function nbind(callback/*, ... args*/) {
* will be provided by Q and appended to these arguments.
* @returns a promise for the value or error
*/
-Q.npost = npost;
-function npost(object, name, args) {
+Q.nmapply = // XXX As proposed by "Redsandro"
+Q.npost = function (object, name, args) {
+ return Q(object).npost(name, args);
+};
+
+Promise.prototype.nmapply = // XXX As proposed by "Redsandro"
+Promise.prototype.npost = function (name, args) {
var nodeArgs = array_slice(args || []);
var deferred = defer();
nodeArgs.push(deferred.makeNodeResolver());
-
- post(object, name, nodeArgs).fail(deferred.reject);
+ this.dispatch("post", [name, nodeArgs]).fail(deferred.reject);
return deferred.promise;
-}
+};
/**
* Calls a method of a Node-style object that accepts a Node-style
@@ -1500,20 +1914,44 @@ function npost(object, name, args) {
* be provided by Q and appended to these arguments.
* @returns a promise for the value or error
*/
-Q.nsend = nsend;
-Q.ninvoke = Q.nsend; // synonyms
-function nsend(object, name /*, ...args*/) {
+Q.nsend = // XXX Based on Mark Miller's proposed "send"
+Q.nmcall = // XXX Based on "Redsandro's" proposal
+Q.ninvoke = function (object, name /*...args*/) {
var nodeArgs = array_slice(arguments, 2);
var deferred = defer();
nodeArgs.push(deferred.makeNodeResolver());
- post(object, name, nodeArgs).fail(deferred.reject);
+ Q(object).dispatch("post", [name, nodeArgs]).fail(deferred.reject);
return deferred.promise;
-}
+};
+Promise.prototype.nsend = // XXX Based on Mark Miller's proposed "send"
+Promise.prototype.nmcall = // XXX Based on "Redsandro's" proposal
+Promise.prototype.ninvoke = function (name /*...args*/) {
+ var nodeArgs = array_slice(arguments, 1);
+ var deferred = defer();
+ nodeArgs.push(deferred.makeNodeResolver());
+ this.dispatch("post", [name, nodeArgs]).fail(deferred.reject);
+ return deferred.promise;
+};
+
+/**
+ * If a function would like to support both Node continuation-passing-style and
+ * promise-returning-style, it can end its internal promise chain with
+ * `nodeify(nodeback)`, forwarding the optional nodeback argument. If the user
+ * elects to use a nodeback, the result will be sent there. If they do not
+ * pass a nodeback, they will receive the result promise.
+ * @param object a result (or a promise for a result)
+ * @param {Function} nodeback a Node.js-style callback
+ * @returns either the promise or nothing
+ */
Q.nodeify = nodeify;
-function nodeify(promise, nodeback) {
+function nodeify(object, nodeback) {
+ return Q(object).nodeify(nodeback);
+}
+
+Promise.prototype.nodeify = function (nodeback) {
if (nodeback) {
- promise.then(function (value) {
+ this.then(function (value) {
nextTick(function () {
nodeback(null, value);
});
@@ -1523,9 +1961,9 @@ function nodeify(promise, nodeback) {
});
});
} else {
- return promise;
+ return this;
}
-}
+};
// All code before this point will be filtered from stack traces.
var qEndingLine = captureLine();