Update Q library

This commit is contained in:
Dan Stillman 2012-12-11 03:22:26 -05:00
parent 0e3d68bdd9
commit 0b89ccadf9

View File

@ -2,8 +2,8 @@
/*jshint browser: true, node: true, /*jshint browser: true, node: true,
curly: true, eqeqeq: true, noarg: true, nonew: true, trailing: true, curly: true, eqeqeq: true, noarg: true, nonew: true, trailing: true,
undef: true */ undef: true */
/*global define: false, Q: true, msSetImmediate: false, setImmediate: false, /*global define: false, Q: true, setImmediate: false,
ReturnValue: false, cajaVM: false, ses: false */ ReturnValue: false, cajaVM: false, ses: false, bootstrap: false */
/*! /*!
* *
* Copyright 2009-2012 Kris Kowal under the terms of the MIT * 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 // Common/Node/RequireJS, the module exports the Q API and when
// executed as a simple <script>, it creates a Q global instead. // executed as a simple <script>, it creates a Q global instead.
// RequireJS // Montage Require
if (typeof define === "function") { if (typeof bootstrap === "function") {
define(definition); bootstrap("promise", definition);
// CommonJS // CommonJS
} else if (typeof exports === "object") { } else if (typeof exports === "object") {
definition(void 0, exports); definition(void 0, exports);
// RequireJS
} else if (typeof define === "function") {
define(definition);
// SES (Secure EcmaScript) // SES (Secure EcmaScript)
} else if (typeof ses !== "undefined") { } else if (typeof ses !== "undefined") {
if (!ses.ok()) { if (!ses.ok()) {
@ -142,6 +146,11 @@
})(function (require, exports) { })(function (require, exports) {
"use strict"; "use strict";
// All code after this point will be filtered from stack traces reported
// by Q.
var qStartingLine = captureLine();
var qFileName;
// shims // shims
// used for fallback "defend" and in "allResolved" // used for fallback "defend" and in "allResolved"
@ -163,12 +172,8 @@ var nextTick;
if (typeof process !== "undefined") { if (typeof process !== "undefined") {
// node // node
nextTick = process.nextTick; 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") { } else if (typeof setImmediate === "function") {
// https://github.com/NobleJS/setImmediate // In IE10, or use https://github.com/NobleJS/setImmediate
nextTick = setImmediate; nextTick = setImmediate;
} else if (typeof MessageChannel !== "undefined") { } else if (typeof MessageChannel !== "undefined") {
// modern browsers // modern browsers
@ -212,7 +217,7 @@ if (Function.prototype.bind) {
uncurryThis = Function_bind.bind(Function_bind.call); uncurryThis = Function_bind.bind(Function_bind.call);
} else { } else {
uncurryThis = function (f) { uncurryThis = function (f) {
return function (thisp) { return function () {
return f.call.apply(f, arguments); return f.call.apply(f, arguments);
}; };
}; };
@ -397,6 +402,14 @@ function formatSourcePosition(frame) {
return line; 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`` * Retrieves an array of structured stack frames parsed from the ``stack``
* property of a given object. * property of a given object.
@ -417,7 +430,7 @@ function getStackFrames(objectWithStack) {
return ( return (
fileName !== "module.js" && fileName !== "module.js" &&
fileName !== "node.js" && fileName !== "node.js" &&
fileName !== qFileName !isInternalFrame(fileName, frame)
); );
}); });
}; };
@ -429,16 +442,17 @@ function getStackFrames(objectWithStack) {
return stack; return stack;
} }
// discover own file name for filtering stack traces // discover own file name and line number range for filtering stack
var qFileName; // traces
if (Error.captureStackTrace) { function captureLine() {
qFileName = (function () { if (Error.captureStackTrace) {
var fileName; var fileName, lineNumber;
var oldPrepareStackTrace = Error.prepareStackTrace; var oldPrepareStackTrace = Error.prepareStackTrace;
Error.prepareStackTrace = function (error, frames) { Error.prepareStackTrace = function (error, frames) {
fileName = frames[0].getFileName(); fileName = frames[1].getFileName();
lineNumber = frames[1].getLineNumber();
}; };
// teases call of temporary prepareStackTrace // teases call of temporary prepareStackTrace
@ -447,17 +461,17 @@ if (Error.captureStackTrace) {
new Error().stack; new Error().stack;
Error.prepareStackTrace = oldPrepareStackTrace; Error.prepareStackTrace = oldPrepareStackTrace;
qFileName = fileName;
return fileName; return lineNumber;
})(); }
} }
function deprecate(fn, name, alternative){ function deprecate(callback, name, alternative) {
return function () { return function () {
if (typeof console !== "undefined" && typeof console.warn === "function"){ if (typeof console !== "undefined" && typeof console.warn === "function") {
console.warn(name + " is deprecated, use " + alternative + " instead."); 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 // forward to the resolved promise. We coerce the resolution value to a
// promise using the ref promise because it handles both fully // promise using the ref promise because it handles both fully
// resolved values and other promises gracefully. // resolved values and other promises gracefully.
var pending = [], value; var pending = [], progressListeners = [], value;
var deferred = object_create(defer.prototype); var deferred = object_create(defer.prototype);
var promise = object_create(makePromise.prototype); var promise = object_create(makePromise.prototype);
promise.promiseSend = function () { promise.promiseSend = function (op, _, __, progress) {
var args = array_slice(arguments); var args = array_slice(arguments);
if (pending) { if (pending) {
pending.push(args); pending.push(args);
if (op === "when" && progress) {
progressListeners.push(progress);
}
} else { } else {
nextTick(function () { nextTick(function () {
value.promiseSend.apply(value, args); value.promiseSend.apply(value, args);
@ -525,6 +542,7 @@ function defer() {
}); });
}, void 0); }, void 0);
pending = void 0; pending = void 0;
progressListeners = void 0;
return value; return value;
} }
@ -535,6 +553,17 @@ function defer() {
deferred.reject = function (exception) { deferred.reject = function (exception) {
return become(reject(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; 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 * @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 * @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 makePromise
*/ */
exports.promise = promise; exports.promise = promise;
function promise(makePromise) { function promise(makePromise) {
var deferred = defer(); var deferred = defer();
call( fcall(
makePromise, makePromise,
void 0,
deferred.resolve, deferred.resolve,
deferred.reject deferred.reject,
deferred.notify
).fail(deferred.reject); ).fail(deferred.reject);
return deferred.promise; return deferred.promise;
} }
@ -610,7 +639,9 @@ function makePromise(descriptor, fallback, valueOf, exception) {
} catch (exception) { } catch (exception) {
result = reject(exception); result = reject(exception);
} }
resolved(result); if (resolved) {
resolved(result);
}
}; };
if (valueOf) { if (valueOf) {
@ -627,8 +658,12 @@ function makePromise(descriptor, fallback, valueOf, exception) {
} }
// provide thenables, CommonJS/Promises/A // provide thenables, CommonJS/Promises/A
makePromise.prototype.then = function (fulfilled, rejected) { makePromise.prototype.then = function (fulfilled, rejected, progressed) {
return when(this, fulfilled, rejected); return when(this, fulfilled, rejected, progressed);
};
makePromise.prototype.thenResolve = function (value) {
return when(this, function () { return value; });
}; };
// Chainable methods // Chainable methods
@ -644,9 +679,12 @@ array_reduce(
"all", "allResolved", "all", "allResolved",
"view", "viewInfo", "view", "viewInfo",
"timeout", "delay", "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 () { makePromise.prototype[name] = function () {
return exports[name].apply( return exports[name].apply(
exports, exports,
@ -726,12 +764,21 @@ function isRejected(object) {
var rejections = []; var rejections = [];
var errors = []; var errors = [];
if (typeof window !== "undefined" && window.console) { var errorsDisplayed;
// This promise library consumes exceptions thrown in handlers so function displayErrors() {
// they can be handled by a subsequent promise. The rejected if (
// promises get added to this array when they are created, and !errorsDisplayed &&
// removed when they are handled. typeof window !== "undefined" &&
console.log("Should be empty:", errors); !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); return rejected ? rejected(exception) : reject(exception);
} }
}, function fallback(op) { }, function fallback() {
return reject(exception); return reject(exception);
}, function valueOf() { }, function valueOf() {
return this; return this;
}, exception); }, exception);
// note that the error has not been handled // note that the error has not been handled
displayErrors();
rejections.push(rejection); rejections.push(rejection);
errors.push(exception); errors.push(exception);
return rejection; return rejection;
@ -778,24 +826,35 @@ function resolve(object) {
if (isPromise(object)) { if (isPromise(object)) {
return 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 // assimilate thenables, CommonJS/Promises/A
if (object && typeof object.then === "function") { if (object && typeof object.then === "function") {
var result = defer(); var deferred = defer();
object.then(result.resolve, result.reject); object.then(deferred.resolve, deferred.reject, deferred.notify);
return result.promise; return deferred.promise;
} }
return makePromise({ return makePromise({
"when": function (rejected) { "when": function () {
return object; return object;
}, },
"get": function (name) { "get": function (name) {
return object[name]; return object[name];
}, },
"put": function (name, value) { "put": function (name, value) {
return object[name] = value; object[name] = value;
return object;
}, },
"del": function (name) { "del": function (name) {
return delete object[name]; delete object[name];
return object;
}, },
"post": function (name, value) { "post": function (name, value) {
return object[name].apply(object, value); return object[name].apply(object, value);
@ -846,7 +905,7 @@ exports.master = master;
function master(object) { function master(object) {
return makePromise({ return makePromise({
"isDef": function () {} "isDef": function () {}
}, function fallback(op) { }, function fallback() {
var args = array_slice(arguments); var args = array_slice(arguments);
return send.apply(void 0, [object].concat(args)); return send.apply(void 0, [object].concat(args));
}, function () { }, function () {
@ -862,7 +921,7 @@ function viewInfo(object, info) {
"viewInfo": function () { "viewInfo": function () {
return info; return info;
} }
}, function fallback(op) { }, function fallback() {
var args = array_slice(arguments); var args = array_slice(arguments);
return send.apply(void 0, [object].concat(args)); return send.apply(void 0, [object].concat(args));
}, function () { }, function () {
@ -906,13 +965,14 @@ function view(object) {
* called, but not both. * called, but not both.
* 3. that fulfilled and rejected will not be called in this turn. * 3. that fulfilled and rejected will not be called in this turn.
* *
* @param value promise or immediate reference to observe * @param value promise or immediate reference to observe
* @param fulfilled function to be called with the fulfilled value * @param fulfilled function to be called with the fulfilled value
* @param rejected function to be called with the rejection exception * @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 * @return promise for the return value from the invoked callback
*/ */
exports.when = when; exports.when = when;
function when(value, fulfilled, rejected) { function when(value, fulfilled, rejected, progressed) {
var deferred = defer(); var deferred = defer();
var done = false; // ensure the untrusted promise makes at most a var done = false; // ensure the untrusted promise makes at most a
// single call to one of the callbacks // single call to one of the callbacks
@ -933,26 +993,30 @@ function when(value, fulfilled, rejected) {
} }
} }
var resolvedValue = resolve(value);
nextTick(function () { nextTick(function () {
resolve(value).promiseSend("when", function (value) { resolvedValue.promiseSend("when", function (value) {
if (done) { if (done) {
return; return;
} }
done = true; done = true;
resolve(value).promiseSend("when", function (value) {
deferred.resolve(_fulfilled(value)); deferred.resolve(_fulfilled(value));
}, function (exception) {
deferred.resolve(_rejected(exception));
});
}, function (exception) { }, function (exception) {
if (done) { if (done) {
return; return;
} }
done = true; done = true;
deferred.resolve(_rejected(exception)); 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; return deferred.promise;
} }
@ -968,8 +1032,10 @@ function when(value, fulfilled, rejected) {
*/ */
exports.spread = spread; exports.spread = spread;
function spread(promise, fulfilled, rejected) { function spread(promise, fulfilled, rejected) {
return when(promise, function (values) { return when(promise, function (valuesOrPromises) {
return fulfilled.apply(void 0, values); return all(valuesOrPromises).then(function (values) {
return fulfilled.apply(void 0, values);
});
}, rejected); }, rejected);
} }
@ -1049,6 +1115,30 @@ function _return(value) {
throw new QReturnValue(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
* 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 * 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. * a promise for an arbitrarily named method like "propfind" in a future turn.
@ -1268,13 +1358,20 @@ function all(promises) {
} }
var deferred = defer(); var deferred = defer();
array_reduce(promises, function (undefined, promise, index) { array_reduce(promises, function (undefined, promise, index) {
when(promise, function (value) { if (isFulfilled(promise)) {
promises[index] = value; promises[index] = valueOf(promise);
if (--countDown === 0) { if (--countDown === 0) {
deferred.resolve(promises); deferred.resolve(promises);
} }
}) } else {
.fail(deferred.reject); when(promise, function (value) {
promises[index] = value;
if (--countDown === 0) {
deferred.resolve(promises);
}
})
.fail(deferred.reject);
}
}, void 0); }, void 0);
return deferred.promise; return deferred.promise;
}); });
@ -1315,6 +1412,20 @@ function fail(promise, rejected) {
return when(promise, void 0, rejected); return when(promise, void 0, 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, * Provides an opportunity to observe the rejection of a promise,
* regardless of whether the promise is fulfilled or rejected. Forwards * regardless of whether the promise is fulfilled or rejected. Forwards
@ -1346,30 +1457,49 @@ function fin(promise, callback) {
* @param {Any*} promise at the end of a chain of promises * @param {Any*} promise at the end of a chain of promises
* @returns nothing * @returns nothing
*/ */
exports.end = end; // XXX stopgap exports.end = deprecate(done, "end", "done"); // XXX deprecated, use done
function end(promise) { exports.done = done;
when(promise, void 0, function (error) { function done(promise, fulfilled, rejected, progress) {
function onUnhandledError(error) {
// forward to a future turn so that ``when`` // forward to a future turn so that ``when``
// does not catch it and turn it into a rejection. // does not catch it and turn it into a rejection.
nextTick(function () { nextTick(function () {
// If possible (that is, if in V8), transform the error stack // If possible (that is, if in V8), transform the error stack
// trace by removing Node and Q cruft, then concatenating with // trace by removing Node and Q cruft, then concatenating with
// the stack trace of the promise we are ``end``ing. See #57. // the stack trace of the promise we are ``done``ing. See #57.
if (Error.captureStackTrace && typeof error === "object" && var errorStackFrames;
"stack" in error) { if (
var errorStackFrames = getStackFrames(error); Error.captureStackTrace &&
typeof error === "object" &&
(errorStackFrames = getStackFrames(error))
) {
var promiseStackFrames = getStackFrames(promise); var promiseStackFrames = getStackFrames(promise);
var combinedStackFrames = errorStackFrames.concat( // Check to make sure the stack trace hasn't already been
"From previous event:", // rendered (possibly by us).
promiseStackFrames if (typeof errorStackFrames !== "string") {
); var combinedStackFrames = errorStackFrames.concat(
error.stack = formatStackTrace(error, combinedStackFrames); "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 * 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. * `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]) * Q.napply(FS.readFile, FS, [__filename])
* .then(function (content) { * .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 * Passes a continuation to a Node function, which is called with a given
* `this` value and arguments provided individually, and returns a promise. * `this` value and arguments provided individually, and returns a promise.
* *
* var FS = require("fs"); * var FS = (require)("fs");
* Q.ncall(FS.readFile, FS, __filename) * Q.ncall(FS.readFile, FS, __filename)
* .then(function (content) { * .then(function (content) {
* }) * })
@ -1452,7 +1582,7 @@ function ncall(callback, thisp /*, ...args*/) {
* *
* Q.nbind(FS.readFile, FS)(__filename) * Q.nbind(FS.readFile, FS)(__filename)
* .then(console.log) * .then(console.log)
* .end() * .done()
* *
*/ */
exports.nbind = nbind; exports.nbind = nbind;
@ -1509,6 +1639,24 @@ function ninvoke(object, name /*, ...args*/) {
return napply(object[name], object, 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();
}); });