commit 0b89ccadf9281e423591c769e7c3164ea5e88c17
parent 0e3d68bdd9858be198c39b0da573eb5b85a85881
Author: Dan Stillman <dstillman@zotero.org>
Date: Tue, 11 Dec 2012 03:22:26 -0500
Update Q library
Diffstat:
| M | resource/q.js | | | 320 | ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--------------------- |
1 file changed, 234 insertions(+), 86 deletions(-)
diff --git a/resource/q.js b/resource/q.js
@@ -2,8 +2,8 @@
/*jshint browser: true, node: true,
curly: true, eqeqeq: true, noarg: true, nonew: true, trailing: true,
undef: true */
-/*global define: false, Q: true, msSetImmediate: false, setImmediate: false,
- ReturnValue: false, cajaVM: false, ses: false */
+/*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
@@ -67,14 +67,18 @@
// Common/Node/RequireJS, the module exports the Q API and when
// executed as a simple <script>, it creates a Q global instead.
- // RequireJS
- if (typeof define === "function") {
- define(definition);
+ // Montage Require
+ if (typeof bootstrap === "function") {
+ bootstrap("promise", definition);
// CommonJS
} else if (typeof exports === "object") {
definition(void 0, exports);
+ // RequireJS
+ } else if (typeof define === "function") {
+ define(definition);
+
// SES (Secure EcmaScript)
} else if (typeof ses !== "undefined") {
if (!ses.ok()) {
@@ -133,7 +137,7 @@
}
};
definition(void 0, Q = {});
-
+
// <script>
} else {
definition(void 0, Q = {});
@@ -142,6 +146,11 @@
})(function (require, exports) {
"use strict";
+// All code after this point will be filtered from stack traces reported
+// by Q.
+var qStartingLine = captureLine();
+var qFileName;
+
// shims
// used for fallback "defend" and in "allResolved"
@@ -163,12 +172,8 @@ var nextTick;
if (typeof process !== "undefined") {
// node
nextTick = process.nextTick;
-} else if (typeof msSetImmediate === "function") {
- // IE 10 only, at the moment
- // And yes, ``bind``ing to ``window`` is necessary O_o.
- nextTick = msSetImmediate.bind(window);
} else if (typeof setImmediate === "function") {
- // https://github.com/NobleJS/setImmediate
+ // In IE10, or use https://github.com/NobleJS/setImmediate
nextTick = setImmediate;
} else if (typeof MessageChannel !== "undefined") {
// modern browsers
@@ -212,7 +217,7 @@ if (Function.prototype.bind) {
uncurryThis = Function_bind.bind(Function_bind.call);
} else {
uncurryThis = function (f) {
- return function (thisp) {
+ return function () {
return f.call.apply(f, arguments);
};
};
@@ -397,6 +402,14 @@ function formatSourcePosition(frame) {
return line;
}
+function isInternalFrame(fileName, frame) {
+ if (fileName !== qFileName) {
+ return false;
+ }
+ var line = frame.getLineNumber();
+ return line >= qStartingLine && line <= qEndingLine;
+}
+
/*
* Retrieves an array of structured stack frames parsed from the ``stack``
* property of a given object.
@@ -417,7 +430,7 @@ function getStackFrames(objectWithStack) {
return (
fileName !== "module.js" &&
fileName !== "node.js" &&
- fileName !== qFileName
+ !isInternalFrame(fileName, frame)
);
});
};
@@ -429,16 +442,17 @@ function getStackFrames(objectWithStack) {
return stack;
}
-// discover own file name for filtering stack traces
-var qFileName;
-if (Error.captureStackTrace) {
- qFileName = (function () {
- var fileName;
+// 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[0].getFileName();
+ fileName = frames[1].getFileName();
+ lineNumber = frames[1].getLineNumber();
};
// teases call of temporary prepareStackTrace
@@ -447,17 +461,17 @@ if (Error.captureStackTrace) {
new Error().stack;
Error.prepareStackTrace = oldPrepareStackTrace;
-
- return fileName;
- })();
+ qFileName = fileName;
+ return lineNumber;
+ }
}
-function deprecate(fn, name, alternative){
+function deprecate(callback, name, alternative) {
return function () {
- if (typeof console !== "undefined" && typeof console.warn === "function"){
- console.warn(name + " is deprecated, use " + alternative + " instead.");
+ if (typeof console !== "undefined" && typeof console.warn === "function") {
+ console.warn(name + " is deprecated, use " + alternative + " instead.", new Error("").stack);
}
- return fn.apply(fn,arguments);
+ return callback.apply(callback, arguments);
};
}
@@ -487,15 +501,18 @@ function defer() {
// forward to the resolved promise. We coerce the resolution value to a
// promise using the ref promise because it handles both fully
// resolved values and other promises gracefully.
- var pending = [], value;
+ var pending = [], progressListeners = [], value;
var deferred = object_create(defer.prototype);
var promise = object_create(makePromise.prototype);
- promise.promiseSend = function () {
+ promise.promiseSend = function (op, _, __, progress) {
var args = array_slice(arguments);
if (pending) {
pending.push(args);
+ if (op === "when" && progress) {
+ progressListeners.push(progress);
+ }
} else {
nextTick(function () {
value.promiseSend.apply(value, args);
@@ -525,6 +542,7 @@ function defer() {
});
}, void 0);
pending = void 0;
+ progressListeners = void 0;
return value;
}
@@ -535,6 +553,17 @@ function defer() {
deferred.reject = function (exception) {
return become(reject(exception));
};
+ deferred.notify = function () {
+ if (pending) {
+ var args = arguments;
+
+ array_reduce(progressListeners, function (undefined, progressListener) {
+ nextTick(function () {
+ progressListener.apply(void 0, args);
+ });
+ }, void 0);
+ }
+ };
return deferred;
}
@@ -561,18 +590,18 @@ defer.prototype.node = deprecate(defer.prototype.makeNodeResolver, "node", "make
/**
* @param makePromise {Function} a function that returns nothing and accepts
- * the resolve and reject functions for a deferred.
+ * 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
*/
exports.promise = promise;
function promise(makePromise) {
var deferred = defer();
- call(
+ fcall(
makePromise,
- void 0,
deferred.resolve,
- deferred.reject
+ deferred.reject,
+ deferred.notify
).fail(deferred.reject);
return deferred.promise;
}
@@ -610,7 +639,9 @@ function makePromise(descriptor, fallback, valueOf, exception) {
} catch (exception) {
result = reject(exception);
}
- resolved(result);
+ if (resolved) {
+ resolved(result);
+ }
};
if (valueOf) {
@@ -627,8 +658,12 @@ function makePromise(descriptor, fallback, valueOf, exception) {
}
// provide thenables, CommonJS/Promises/A
-makePromise.prototype.then = function (fulfilled, rejected) {
- return when(this, fulfilled, rejected);
+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
@@ -644,9 +679,12 @@ array_reduce(
"all", "allResolved",
"view", "viewInfo",
"timeout", "delay",
- "catch", "finally", "fail", "fin", "end"
+ "catch", "finally", "fail", "fin", "progress", "end", "done",
+ "ncall", "napply", "nbind",
+ "npost", "ninvoke",
+ "nend"
],
- function (prev, name) {
+ function (undefined, name) {
makePromise.prototype[name] = function () {
return exports[name].apply(
exports,
@@ -726,12 +764,21 @@ function isRejected(object) {
var rejections = [];
var errors = [];
-if (typeof window !== "undefined" && window.console) {
- // This promise library consumes exceptions thrown in handlers so
- // they can be handled by a subsequent promise. The rejected
- // promises get added to this array when they are created, and
- // removed when they are handled.
- console.log("Should be empty:", errors);
+var errorsDisplayed;
+function displayErrors() {
+ if (
+ !errorsDisplayed &&
+ 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);
+ }
+ errorsDisplayed = true;
}
/**
@@ -753,12 +800,13 @@ function reject(exception) {
}
return rejected ? rejected(exception) : reject(exception);
}
- }, function fallback(op) {
+ }, function fallback() {
return reject(exception);
}, function valueOf() {
return this;
}, exception);
// note that the error has not been handled
+ displayErrors();
rejections.push(rejection);
errors.push(exception);
return rejection;
@@ -778,24 +826,35 @@ function resolve(object) {
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 result = defer();
- object.then(result.resolve, result.reject);
- return result.promise;
+ var deferred = defer();
+ object.then(deferred.resolve, deferred.reject, deferred.notify);
+ return deferred.promise;
}
return makePromise({
- "when": function (rejected) {
+ "when": function () {
return object;
},
"get": function (name) {
return object[name];
},
"put": function (name, value) {
- return object[name] = value;
+ object[name] = value;
+ return object;
},
"del": function (name) {
- return delete object[name];
+ delete object[name];
+ return object;
},
"post": function (name, value) {
return object[name].apply(object, value);
@@ -846,7 +905,7 @@ exports.master = master;
function master(object) {
return makePromise({
"isDef": function () {}
- }, function fallback(op) {
+ }, function fallback() {
var args = array_slice(arguments);
return send.apply(void 0, [object].concat(args));
}, function () {
@@ -862,7 +921,7 @@ function viewInfo(object, info) {
"viewInfo": function () {
return info;
}
- }, function fallback(op) {
+ }, function fallback() {
var args = array_slice(arguments);
return send.apply(void 0, [object].concat(args));
}, function () {
@@ -906,13 +965,14 @@ function view(object) {
* 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 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
*/
exports.when = when;
-function when(value, fulfilled, rejected) {
+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
@@ -933,26 +993,30 @@ function when(value, fulfilled, rejected) {
}
}
+ var resolvedValue = resolve(value);
nextTick(function () {
- resolve(value).promiseSend("when", function (value) {
+ resolvedValue.promiseSend("when", function (value) {
if (done) {
return;
}
done = true;
- resolve(value).promiseSend("when", function (value) {
- deferred.resolve(_fulfilled(value));
- }, function (exception) {
- deferred.resolve(_rejected(exception));
- });
+
+ deferred.resolve(_fulfilled(value));
}, 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);
+ }
+
return deferred.promise;
}
@@ -968,8 +1032,10 @@ function when(value, fulfilled, rejected) {
*/
exports.spread = spread;
function spread(promise, fulfilled, rejected) {
- return when(promise, function (values) {
- return fulfilled.apply(void 0, values);
+ return when(promise, function (valuesOrPromises) {
+ return all(valuesOrPromises).then(function (values) {
+ return fulfilled.apply(void 0, values);
+ });
}, rejected);
}
@@ -1050,6 +1116,30 @@ function _return(value) {
}
/**
+ * The promised function decorator ensures that any promise arguments
+ * are resolved and passed as values (`this` is also resolved and passed
+ * as a value). It will also ensure that the result of a function is
+ * always a promise.
+ *
+ * @example
+ * var add = Q.promised(function (a, b) {
+ * return a + b;
+ * });
+ * add(Q.resolve(a), Q.resolve(B));
+ *
+ * @param {function} callback The function to decorate
+ * @returns {function} a function that has been decorated.
+ */
+exports.promised = promised;
+function promised(callback) {
+ return function () {
+ return all([this, all(arguments)]).spread(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.
*/
@@ -1268,13 +1358,20 @@ function all(promises) {
}
var deferred = defer();
array_reduce(promises, function (undefined, promise, index) {
- when(promise, function (value) {
- promises[index] = value;
+ if (isFulfilled(promise)) {
+ promises[index] = valueOf(promise);
if (--countDown === 0) {
deferred.resolve(promises);
}
- })
- .fail(deferred.reject);
+ } else {
+ when(promise, function (value) {
+ promises[index] = value;
+ if (--countDown === 0) {
+ deferred.resolve(promises);
+ }
+ })
+ .fail(deferred.reject);
+ }
}, void 0);
return deferred.promise;
});
@@ -1316,6 +1413,20 @@ function fail(promise, rejected) {
}
/**
+ * Attaches a listener that can respond to progress notifications from a
+ * promise's originating deferred. This listener receives the exact arguments
+ * passed to ``deferred.notify``.
+ * @param {Any*} promise for something
+ * @param {Function} callback to receive any progress notifications
+ * @returns the given promise, unchanged
+ */
+exports.progress = progress;
+function progress(promise, progressed) {
+ when(promise, void 0, void 0, progressed);
+ return promise;
+}
+
+/**
* Provides an opportunity to observe the rejection of a promise,
* regardless of whether the promise is fulfilled or rejected. Forwards
* the resolution to the returned promise when the callback is done.
@@ -1346,30 +1457,49 @@ function fin(promise, callback) {
* @param {Any*} promise at the end of a chain of promises
* @returns nothing
*/
-exports.end = end; // XXX stopgap
-function end(promise) {
- when(promise, void 0, function (error) {
+exports.end = deprecate(done, "end", "done"); // XXX deprecated, use done
+exports.done = done;
+function done(promise, fulfilled, rejected, progress) {
+ function onUnhandledError(error) {
// forward to a future turn so that ``when``
// does not catch it and turn it into a rejection.
nextTick(function () {
// If possible (that is, if in V8), transform the error stack
// trace by removing Node and Q cruft, then concatenating with
- // the stack trace of the promise we are ``end``ing. See #57.
- if (Error.captureStackTrace && typeof error === "object" &&
- "stack" in error) {
- var errorStackFrames = getStackFrames(error);
+ // 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);
- var combinedStackFrames = errorStackFrames.concat(
- "From previous event:",
- promiseStackFrames
- );
- error.stack = formatStackTrace(error, combinedStackFrames);
+ // 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);
+ }
}
- throw error;
+ if (exports.onerror) {
+ exports.onerror(error);
+ } else {
+ throw error;
+ }
});
- });
+ }
+
+ // Avoid unnecessary `nextTick`ing via an unnecessary `when`.
+ var promiseToHandle = fulfilled || rejected || progress ?
+ when(promise, fulfilled, rejected, progress) :
+ promise;
+
+ fail(promiseToHandle, onUnhandledError);
}
/**
@@ -1419,7 +1549,7 @@ 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.
*
- * var FS = require("fs");
+ * var FS = (require)("fs");
* Q.napply(FS.readFile, FS, [__filename])
* .then(function (content) {
* })
@@ -1434,7 +1564,7 @@ function napply(callback, thisp, args) {
* Passes a continuation to a Node function, which is called with a given
* `this` value and arguments provided individually, and returns a promise.
*
- * var FS = require("fs");
+ * var FS = (require)("fs");
* Q.ncall(FS.readFile, FS, __filename)
* .then(function (content) {
* })
@@ -1452,7 +1582,7 @@ function ncall(callback, thisp /*, ...args*/) {
*
* Q.nbind(FS.readFile, FS)(__filename)
* .then(console.log)
- * .end()
+ * .done()
*
*/
exports.nbind = nbind;
@@ -1509,6 +1639,24 @@ function ninvoke(object, name /*, ...args*/) {
return napply(object[name], object, args);
}
-defend(exports);
+exports.nend = nend;
+function nend(promise, nodeback) {
+ if (nodeback) {
+ promise.then(function (value) {
+ nextTick(function () {
+ nodeback(null, value);
+ });
+ }, function (error) {
+ nextTick(function () {
+ nodeback(error);
+ });
+ });
+ } else {
+ return promise;
+ }
+}
+
+// All code before this point will be filtered from stack traces.
+var qEndingLine = captureLine();
});