www

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

commit 0b89ccadf9281e423591c769e7c3164ea5e88c17
parent 0e3d68bdd9858be198c39b0da573eb5b85a85881
Author: Dan Stillman <dstillman@zotero.org>
Date:   Tue, 11 Dec 2012 03:22:26 -0500

Update Q library

Diffstat:
Mresource/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(); });