UNPKG

jolokia

Version:
1,674 lines (1,440 loc) 79.9 kB
/** * Jolokia JavaScript client library * * Version 1.0.6 * * GitHub repository can be found at https://github.com/janjakubnanista/jolokia-js-client * * Released under MIT license * * Created by Ján Jakub Naništa <jan.jakub.nanista@gmail.com> */ (function webpackUniversalModuleDefinition(root, factory) { if(typeof exports === 'object' && typeof module === 'object') module.exports = factory(require("$")); else if(typeof define === 'function' && define.amd) define(["$"], factory); else if(typeof exports === 'object') exports["Jolokia"] = factory(require("$")); else root["Jolokia"] = factory(root["$"]); })(this, function(__WEBPACK_EXTERNAL_MODULE_1__) { return /******/ (function(modules) { // webpackBootstrap /******/ // The module cache /******/ var installedModules = {}; /******/ /******/ // The require function /******/ function __webpack_require__(moduleId) { /******/ /******/ // Check if module is in cache /******/ if(installedModules[moduleId]) /******/ return installedModules[moduleId].exports; /******/ /******/ // Create a new module (and put it into the cache) /******/ var module = installedModules[moduleId] = { /******/ exports: {}, /******/ id: moduleId, /******/ loaded: false /******/ }; /******/ /******/ // Execute the module function /******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); /******/ /******/ // Flag the module as loaded /******/ module.loaded = true; /******/ /******/ // Return the exports of the module /******/ return module.exports; /******/ } /******/ /******/ /******/ // expose the modules object (__webpack_modules__) /******/ __webpack_require__.m = modules; /******/ /******/ // expose the module cache /******/ __webpack_require__.c = installedModules; /******/ /******/ // __webpack_public_path__ /******/ __webpack_require__.p = ""; /******/ /******/ // Load entry module and return exports /******/ return __webpack_require__(0); /******/ }) /************************************************************************/ /******/ ([ /* 0 */ /***/ function(module, exports, __webpack_require__) { 'use strict'; var BaseJolokia = __webpack_require__(2); var $ = __webpack_require__(1); var q = __webpack_require__(4); var utils = __webpack_require__(3); var Jolokia = module.exports = function() { BaseJolokia.apply(this, arguments); }; utils.inherit(Jolokia, BaseJolokia); Jolokia.prototype.httpRequest = function(options) { options.processData = false; options.dataType = options.dataType || 'json'; if (options.auth) { var username = options.auth.username, password = options.auth.password; // Create base64 encoded authorization token options.headers = { Authorization: 'Basic ' + utils.base64encode(username + ':' + password) }; // Add appropriate field for CORS access options.xhrFields = { // Please note that for CORS access with credentials, the request // must be asynchronous (see https://dvcs.w3.org/hg/xhr/raw-file/tip/Overview.html#the-withcredentials-attribute) // It works synchronously in Chrome nevertheless, but fails in Firefox. withCredentials: true }; delete options.auth; } return q($.ajax(options)); }; /***/ }, /* 1 */ /***/ function(module, exports, __webpack_require__) { module.exports = $; /***/ }, /* 2 */ /***/ function(module, exports, __webpack_require__) { 'use strict'; var utils = __webpack_require__(3); var q = __webpack_require__(4); /** * Constructor method. * * Accepts one argument, either an URL of backend * to use with requests or hash of options (refer to documentation of Jolokia.request * for available options). * * @param {String|Object} options Hash of options or String URL to use with requests */ var Jolokia = module.exports = function(options) { if (typeof(options) === 'string') { options = { url: options }; } options = utils.extend({}, options); this.options = function() { return utils.extend({}, options); }; }; /** * Escapes !, " and /, then URI encodes string * * @param {String} string String to be escaped * @return {String} Escaped string */ Jolokia.escape = function(string) { return encodeURIComponent(string.replace(/(["!\/])/g, '!$1')); }; /** * Returns true if passed response object represents an error state * * @param {Object} response Jolokia server response * @return {Boolean} True if response object represents an error */ Jolokia.isError = function(response) { return !response.status || response.status !== 200; }; /** * Converts a value to string to be used to construct URL for GET requests. * Value is not escaped though, it must be done separately via Jolokia.escape method * * @param {Any} value Value to be converted * @return {String} String representation of the value */ Jolokia.toString = function(value) { if (utils.isArray(value)) { return value.map(valueToString).join(','); } else { return valueToString(value); } }; /** * Automatically determine HTTP request method to be used for particular request. * This follows the specification for jolokia requests that can be found at * http://www.jolokia.org/reference/html/clients.html#js-request-sync-async * * @param {Object|Array} request Jolokia request * @return {String} Lowercase HTTP method, either post or get */ Jolokia.prototype.determineRequestMethod = function(request) { var method = utils.isArray(request) || request.config || (request.type.match(/read/i) && utils.isArray(request.attribute)) || request.target ? 'post' : 'get'; return method; }; /** * Create an url to be used with GET requests * * @param {Object} request Jolokia request * @return {String} URL to be used with HTTP GET request */ Jolokia.prototype.constructUrl = function(request) { var type = request.type.toLowerCase(); var urlParts = []; var urlSuffix = ''; if (type === 'read') { if (request.attribute) { urlParts = [request.mbean, request.attribute]; urlSuffix = request.path; } else { urlParts = [request.mbean]; } } else if (type === 'write') { urlParts = [request.mbean, request.attribute, Jolokia.toString(request.value)]; urlSuffix = request.path; } else if (type === 'exec') { urlParts = [request.mbean, request.operation]; if (request.arguments) { urlParts = urlParts.concat(request.arguments.map(Jolokia.toString)); } } else if (type === 'search') { urlParts = [request.mbean]; } else if (type === 'list') { urlSuffix = request.path; } if (utils.isArray(urlSuffix)) { urlSuffix = urlSuffix.map(Jolokia.escape).join('/'); } // Add request operation at the beginning of the URL urlParts.unshift(type); urlParts = urlParts.map(Jolokia.escape); urlSuffix = urlSuffix ? urlSuffix.replace(/^\//, '') : ''; return urlParts.join('/') + '/' + urlSuffix; }; /** * Central method used to create HTTP requests * * @param {Object|Array} request Jolokia request object or an array of objects * @param {Object} options [optional] Additional options for request * @return {jQuery.Promise} jQuery jXHR promise resolved with an array of jolokia server response objects */ Jolokia.prototype.request = function (request, options) { options = utils.extend({}, this.options(), options); if (!options.url || typeof(options.url) !== 'string') { throw 'Invalid URL : ' + options.url; } var method = (options.method || this.determineRequestMethod(request)).toLowerCase(); if (method !== 'get' && method !== 'post') { throw new Error('Unsupported request method: ' + method); } if (method === 'get') { if (utils.isArray(request)) { throw new Error('Cannot use GET with bulk requests'); } if (request.type.match(/read/i) && utils.isArray(request.attribute)) { throw new Error('Cannot use GET for read with multiple attributes'); } if (request.target) { throw new Error('Cannot use GET request with proxy mode'); } if (request.config) { throw new Error('Cannot use GET with request specific config'); } } // Add trailing slash to URL options.url = options.url.replace(/\/*$/, '/'); if (method === 'get') { options.url += this.constructUrl(request, options); } else { options.data = JSON.stringify(request); } // Add request method and URL options.method = method; options.url = addQueryParamsToUrl(options.url, options.query || {}); return this.httpRequest(options).then(function(response) { var responses = utils.isArray(response) ? response: [response]; if (typeof(options.success) === 'function') { options.success.call(this, responses); } return responses; }.bind(this), options.error); }; /** * Get one or more attributes * * @param {String} mbean name of MBean to query. Can be a pattern. * @param {String} attribute attribute name. If an array, multiple attributes are fetched. * If <code>null</code>, all attributes are fetched. * @param {String} path optional path within the return value. * For multi-attribute fetch, the path is ignored. * @param {Object} options options passed to Jolokia.request() * * @return {jQuery.Promise} */ Jolokia.prototype.get = function(mbean, attribute, path, options) { if (arguments.length === 3 && utils.isObject(path)) { options = path; path = null; } else if (arguments.length === 2 && utils.isObject(attribute)) { options = attribute; attribute = null; path = null; } var request = { type: 'read', mbean: mbean, attribute: attribute }; if (path) { request.path = path; } return this.request(request, options).then(returnValueOrThrow); }; /** * Set an attribute on a MBean. * * @param mbean objectname of MBean to set * @param attribute the attribute to set * @param value the value to set * @param path an optional <em>inner path</em> which, when given, is used to determine * an inner object to set the value on * @param opts additional options passed to Jolokia.request() * @return the previous value */ Jolokia.prototype.set = function(mbean, attribute, value, path, options) { if (arguments.length === 4 && utils.isObject(path)) { options = path; path = null; } var request = { type: 'write', mbean: mbean, attribute: attribute, value: value }; if (path) { request.path = path; } return this.request(request, options).then(returnValueOrThrow); }; /** * Execute a JMX operation and return the result value * * @param mbean objectname of the MBean to operate on * @param operation name of operation to execute. Can contain a signature in case overloaded * operations are to be called (comma separated fully qualified argument types * append to the operation name within parentheses) * @param arg1, arg2, ..... one or more argument required for executing the operation. * @param opts optional options for Jolokia.request() (must be an object) * @return the return value of the JMX operation. */ Jolokia.prototype.execute = function(mbean, operation) { var request = { type: 'exec', mbean: mbean, operation: operation }; var options, args = Array.prototype.slice.call(arguments, 0); if (args.length > 2 && utils.isObject(args[args.length - 1])) { options = args.pop(); } if (args.length > 2) { request.arguments = args.slice(2); } return this.request(request, options).then(returnValueOrThrow); }; /** * Search for MBean based on a pattern and return a reference to the list of found * MBeans names (as string). If no MBean can be found, <code>null</code> is returned. For * example, * * jolokia.search("*:j2eeType=J2EEServer,*") * * searches all MBeans whose name are matching this pattern, which are according * to JSR77 all application servers in all available domains. * * @param mbeanPattern pattern to search for * @param opts optional options for Jolokia.request() * @return an array with ObjectNames as string */ Jolokia.prototype.search = function(mbeanPattern, options) { var request = { type: 'search', mbean: mbeanPattern }; return this.request(request, options).then(returnValueOrThrow); }; /** * This method return the version of the agent and the Jolokia protocol * version as part of an object. If available, server specific information * like the application server's name are returned as wel. * A typical response looks like * * <pre> * { * protocol: "4.0", * agent: "0.82", * info: { * product: "glassfish", * vendor": "Sun", * extraInfo: { * amxBooted: false * } * } * </pre> * * @param opts optional options for Jolokia.request() * @param version and other meta information as object */ Jolokia.prototype.version = function(options) { return this.request({ type: 'version' }, options).then(returnValueOrThrow); }; /** * Get all MBeans as registered at the specified server. A C<$path> can be * specified in order to fetchy only a subset of the information. When no path is * given, the returned value has the following format * * <pre> * { * &lt;domain&gt; : * { * &lt;canonical property list&gt; : * { * "attr" : * { * &lt;atrribute name&gt; : * { * desc : &lt;description of attribute&gt; * type : &lt;java type&gt;, * rw : true/false * }, * .... * }, * "op" : * { * &lt;operation name&gt; : * { * "desc" : &lt;description of operation&gt; * "ret" : &lt;return java type&gt; * "args" : * [ * { * "desc" : &lt;description&gt;, * "name" : &lt;name&gt;, * "type" : &lt;java type&gt; * }, * .... * ] * }, * .... * }, * .... * } * .... * } * </pre> * * A complete path has the format &lt;domain&gt;/property * list&gt;/("attribute"|"operation")/&lt;index&gt;"> * (e.g. <code>java.lang/name=Code Cache,type=MemoryPool/attribute/0</code>). A path can be * provided partially, in which case the remaining map/array is returned. The path given must * be already properly escaped (i.e. slashes must be escaped like <code>!/</code> and exlamation * marks like <code>!!</code>. * See also the Jolokia Reference Manual for a more detailed discussion of inner paths and escaping. * * * @param path optional path for diving into the list * @param opts optional opts passed to Jolokia.request() */ Jolokia.prototype.list = function(path, options) { if (arguments.length === 1 && !utils.isArray(path) && utils.isObject(path)) { options = path; path = null; } var request = { type: 'list' }; if (path) { request.path = path; } return this.request(request, options).then(returnValueOrThrow); }; /** * Private helper function. * Returns the value attribute from server response or throws an exception * in case of error. * * @param {Array} responses Jolokia server responses * @return {Mixed} Response value */ function returnValueOrThrow(responses) { if (responses[0].status !== 200) { return q.reject(responses[0]); } return responses[0].value; } /** * Private helper function. * Serializes value to be passed over to Jolokia backend * following Jolokia protocol. * * @param {Object} value Value to serialize * @return {String} Serialized value */ function valueToString(value) { if (value === null || value === undefined) return '[null]'; if (value === '') return '""'; return value.toString(); } /** * Private helper function. * Adds URL query parameters to URL. * * @param {String} url URL * @param {Object} params Hash containing query parameters */ function addQueryParamsToUrl(url, params) { var query = []; for (var key in params) { query.push(key + '=' + params[key]); } query = query.join('&'); if (query) { if (url.indexOf('?') === -1) { url = url + '?' + query; } else { url = url.replace('?', '?' + query + '&'); } } return url; } /***/ }, /* 3 */ /***/ function(module, exports, __webpack_require__) { 'use strict'; /** * Mix multiple objects into target object. * * @param {Object} target Target object * @param {...Object} mixins Mixins to enhance target object with * @return {Object} Target object */ exports.extend = function(target) { var mixins = Array.prototype.slice.call(arguments, 1); var getter, setter; return mixins.reduce(function(target, mixin) { if (mixin === null || typeof(mixin) !== 'object') return target; return Object.keys(mixin).reduce(function(target, key) { getter = mixin.__lookupGetter__(key); setter = mixin.__lookupSetter__(key); if (getter || setter) { if (getter) target.__defineGetter__(key, getter); if (setter) target.__defineSetter__(key, setter); } else { target[key] = mixin[key]; } return target; }, target); }, target); }; exports.isArray = function(object) { return Object.prototype.toString.call(object) === '[object Array]'; }; exports.isObject = function(object) { return object !== null && typeof(object) === 'object'; }; exports.base64encode = function(string) { if (typeof(window) !== 'undefined') { return window.btoa(string); } if (typeof('Buffer') !== undefined) { return new Buffer(string, 'utf8').toString('base64'); } throw new Error('Unable to find window.btoa() or Buffer to convert to Base64'); }; exports.inherit = function(subClass, superClass) { // Inherit from BaseJolokia subClass.prototype = Object.create(superClass.prototype); subClass.prototype.constructor = subClass; for (var i in superClass) { if (typeof(superClass[i]) === 'function') { subClass[i] = superClass[i]; } } return subClass; }; /***/ }, /* 4 */ /***/ function(module, exports, __webpack_require__) { // vim:ts=4:sts=4:sw=4: /*! * * Copyright 2009-2012 Kris Kowal under the terms of the MIT * license found at http://github.com/kriskowal/q/raw/master/LICENSE * * With parts by Tyler Close * Copyright 2007-2009 Tyler Close under the terms of the MIT X license found * at http://www.opensource.org/licenses/mit-license.html * Forked at ref_send.js version: 2009-05-11 * * With parts by Mark Miller * Copyright (C) 2011 Google Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ (function (definition) { "use strict"; // This file will function properly as a <script> tag, or a module // using CommonJS and NodeJS or RequireJS module formats. In // Common/Node/RequireJS, the module exports the Q API and when // executed as a simple <script>, it creates a Q global instead. // Montage Require if (typeof bootstrap === "function") { bootstrap("promise", definition); // CommonJS } else if (true) { module.exports = definition(); // RequireJS } else if (typeof define === "function" && define.amd) { define(definition); // SES (Secure EcmaScript) } else if (typeof ses !== "undefined") { if (!ses.ok()) { return; } else { ses.makeQ = definition; } // <script> } else if (typeof self !== "undefined") { self.Q = definition(); } else { throw new Error("This environment was not anticiapted by Q. Please file a bug."); } })(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(); var qFileName; // shims // used for fallback in "allResolved" var noop = function () {}; // Use the fastest possible means to execute a task in a future turn // of the event loop. 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(); } 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); } } if (domain) { domain.exit(); } } flushing = false; } nextTick = function (task) { tail = tail.next = { task: task, domain: isNodeJS && process.domain, next: null }; 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 { requestTick = function () { setImmediate(flush); }; } } else if (typeof MessageChannel !== "undefined") { // modern browsers // http://www.nonblocking.io/2011/06/windownexttick.html var channel = new MessageChannel(); // At least Safari Version 6.0.5 (8536.30.1) intermittently cannot create // working message ports the first time a page loads. channel.port1.onmessage = function () { requestTick = requestPortTick; channel.port1.onmessage = flush; flush(); }; var requestPortTick = function () { // Opera requires us to provide a message payload, regardless of // whether we use it. channel.port2.postMessage(0); }; requestTick = function () { setTimeout(flush, 0); requestPortTick(); }; } else { // old browsers requestTick = function () { setTimeout(flush, 0); }; } return nextTick; })(); // Attempt to make generics safe in the face of downstream // modifications. // There is no situation where this is necessary. // If you need a security guarantee, these primordials need to be // deeply frozen anyway, and if you don’t need a security guarantee, // this is just plain paranoid. // However, this **might** have the nice side-effect of reducing the size of // the minified code by reducing x.call() to merely x() // 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) { return function () { return call.apply(f, arguments); }; } // This is equivalent, but slower: // uncurryThis = Function_bind.bind(Function_bind.call); // http://jsperf.com/uncurrythis var array_slice = uncurryThis(Array.prototype.slice); var array_reduce = uncurryThis( Array.prototype.reduce || function (callback, basis) { var index = 0, length = this.length; // concerning the initial value, if one is not provided if (arguments.length === 1) { // seek to the first value in the array, accounting // for the possibility that is is a sparse array do { if (index in this) { basis = this[index++]; break; } if (++index >= length) { throw new TypeError(); } } while (1); } // reduce for (; index < length; index++) { // account for the possibility that the array is sparse if (index in this) { basis = callback(basis, this[index], index); } } return basis; } ); var array_indexOf = uncurryThis( Array.prototype.indexOf || function (value) { // not a very good shim, but good enough for our one use of it for (var i = 0; i < this.length; i++) { if (this[i] === value) { return i; } } return -1; } ); var array_map = uncurryThis( Array.prototype.map || function (callback, thisp) { var self = this; var collect = []; array_reduce(self, function (undefined, value, index) { collect.push(callback.call(thisp, value, index, self)); }, void 0); return collect; } ); var object_create = Object.create || function (prototype) { function Type() { } Type.prototype = prototype; return new Type(); }; var object_hasOwnProperty = uncurryThis(Object.prototype.hasOwnProperty); var object_keys = Object.keys || function (object) { var keys = []; for (var key in object) { if (object_hasOwnProperty(object, key)) { keys.push(key); } } return keys; }; 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]" || exception instanceof QReturnValue ); } // FIXME: Remove this helper and Q.return once ES6 generators are in // SpiderMonkey. var QReturnValue; if (typeof ReturnValue !== "undefined") { QReturnValue = ReturnValue; } else { QReturnValue = function (value) { this.value = value; }; } // long stack traces var STACK_JUMP_SEPARATOR = "From previous event:"; function makeStackTraceLong(error, promise) { // 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 ) { 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); } } function filterStackString(stackString) { var lines = stackString.split("\n"); var desiredLines = []; for (var i = 0; i < lines.length; ++i) { var line = lines[i]; if (!isInternalFrame(line) && !isNodeFrame(line) && line) { desiredLines.push(line); } } return desiredLines.join("\n"); } function isNodeFrame(stackLine) { return stackLine.indexOf("(module.js:") !== -1 || 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 fileNameAndLineNumber = getFileNameAndLineNumber(stackLine); if (!fileNameAndLineNumber) { return false; } var fileName = fileNameAndLineNumber[0]; var lineNumber = fileNameAndLineNumber[1]; return fileName === qFileName && lineNumber >= qStartingLine && lineNumber <= qEndingLine; } // discover own file name and line number range for filtering stack // traces function captureLine() { if (!hasStacks) { return; } 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; } 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); } return callback.apply(callback, arguments); }; } // end of shims // beginning of real work /** * 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) { // 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 (value instanceof Promise) { 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. * @param {Function} task */ Q.nextTick = nextTick; /** * Controls whether or not long stack traces will be on */ Q.longStackSupport = false; // enable long stacks if Q_DEBUG is set if (typeof process === "object" && process && process.env && process.env.Q_DEBUG) { Q.longStackSupport = true; } /** * Constructs a {promise, resolve, reject} object. * * `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 "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 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 `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(Promise.prototype); promise.promiseDispatch = function (resolve, op, operands) { var args = array_slice(arguments); if (messages) { messages.push(args); if (op === "when" && operands[1]) { // progress operand progressListeners.push(operands[1]); } } else { Q.nextTick(function () { resolvedPromise.promiseDispatch.apply(resolvedPromise, args); }); } }; // XXX deprecated promise.valueOf = function () { if (messages) { return promise; } var nearerValue = nearer(resolvedPromise); if (isPromise(nearerValue)) { resolvedPromise = nearerValue; // shorten chain } return nearerValue; }; promise.inspect = function () { if (!resolvedPromise) { return { state: "pending" }; } return resolvedPromise.inspect(); }; 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); } } // 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) { Q.nextTick(function () { newPromise.promiseDispatch.apply(newPromise, message); }); }, void 0); messages = void 0; progressListeners = void 0; } deferred.promise = promise; deferred.resolve = function (value) { if (resolvedPromise) { return; } become(Q(value)); }; deferred.fulfill = function (value) { if (resolvedPromise) { return; } become(fulfill(value)); }; deferred.reject = function (reason) { if (resolvedPromise) { return; } become(reject(reason)); }; deferred.notify = function (progress) { if (resolvedPromise) { return; } array_reduce(progressListeners, function (undefined, progressListener) { Q.nextTick(function () { progressListener(progress); }); }, void 0); }; return deferred; } /** * Creates a Node-style callback that will resolve or reject the deferred * promise. * @returns a nodeback */ defer.prototype.makeNodeResolver = function () { var self = this; return function (error, value) { if (error) { self.reject(error); } else if (arguments.length > 2) { self.resolve(array_slice(arguments, 1)); } else { self.resolve(value); } }; }; /** * @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 resolver */ Q.Promise = promise; // ES6 Q.promise = promise; function promise(resolver) { if (typeof resolver !== "function") { throw new TypeError("resolver must be a function."); } var deferred = defer(); try { resolver(deferred.resolve, deferred.reject, deferred.notify); } catch (reason) { deferred.reject(reason); } return deferred.promise; } promise.race = race; // ES6 promise.all = all; // ES6 promise.reject = reject; // ES6 promise.resolve = Q; // ES6 // 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 settled. * @param answers {Array[Any*]} promises to race * @returns {Any*} the first promise to be settled */ 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), * 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 * provided with the proper name. The API makes no guarantees about the nature * of the returned object, apart from that it is usable whereever promises are * bought and sold. */ 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 )); }; } if (inspect === void 0) { inspect = function () { return {state: "unknown"}; }; } var promise = object_create(Promise.prototype); promise.promiseDispatch = function (resolve, op, args) { var result; try { if (descriptor[op]) { result = descriptor[op].apply(promise, args); } else { result = fallback.call(promise, op, args); } } catch (exception) { result = reject(exception); } if (resolve) { resolve(result); } }; promise.inspect = inspect; // XXX deprecated `valueOf` and `exception` support if (inspect) { var inspected = inspect(); if (inspected.state === "rejected") { promise.exception = inspected.reason; } promise.valueOf = function () { var inspected = inspect(); if (inspected.state === "pending" || inspected.state === "rejected") { return promise; } return inspected.value; }; } return promise; } 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; } Q.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); } }]); return deferred.promise; }; Q.tap = function (promise, callback) { return Q(promise).tap(callback); }; /** * Works almost like "finally", but not called for rejections. * Original resolution value is passed through callback unaffected. * Callback may return a promise that will be awaited for. * @param {Function} callback * @returns {Q.Promise} * @example * doSomething() * .then(...) * .tap(console.log) * .then(...); */ Promise.prototype.tap = function (callback) { callback = Q(callback); return this.then(function (value) { return callback.fcall(value).thenResolve(value); }); }; /** * 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); }; /** * If an object is not a promise, it is as "near" as possible. * If a promise is rejected, it is as "near" as possible too. * If it’s a fulfilled promise, the fulfillment value is nearer. * If it’s a deferred promise and the deferred has been resolved, the * resolution is "nearer". * @param object * @returns most resolved (nearest) form of the object */ // XXX should we re-do this? Q.nearer = nearer; function nearer(value) { if (isPromise(value)) { var inspected = value.inspect(); if (inspected.state === "fulfilled") { return inspected.value; } } return value; } /** * @returns whether the given object is a promise. * Otherwise it is a fulfilled value. */ Q.isPromise = isPromise; function isPromise(object) { return object instanceof Promise; } Q.isPromiseAlike = isPromiseAlike; function isPromiseAlike(object) { return isObject(object) && typeof object.then === "function"; } /** * @returns whether the given object is a pending promise, meaning not * fulfilled or rejected. */ Q.isPending = isPending; function isPending(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 !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) { return isPromise(object) && object.inspect().state === "rejected"; } 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 trackUnhandledRejections = true; function resetUnhandledRejections() { unhandledReasons.length = 0; unhandledRejections.length = 0; if (!trackUnhandledRejections) { trackUnhandledRejections = true; } } function trackRejection(promise, reason) { if (!trackUnhandledRejections) { return; } unhandledRejections.push(promise); if (reason && typeof reason.stack !== "undefined") { unhandledReasons.push(reason.stack); } else { unhandledReasons.push("(no stack) " + reason); } } function untrackRejection(promise) { if (!trackUnhandledRejections) { return; } var at = array_indexOf(unh