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,
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);
}
@ -1049,6 +1115,30 @@ 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
* 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;
});
@ -1315,6 +1412,20 @@ function fail(promise, 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,
* 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
* @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();
});