All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.google.javascript.jscomp.js.es6.promise.js Maven / Gradle / Ivy

Go to download

Closure Compiler is a JavaScript optimizing compiler. It parses your JavaScript, analyzes it, removes dead code and rewrites and minimizes what's left. It also checks syntax, variable references, and types, and warns about common JavaScript pitfalls. It is used in many of Google's JavaScript apps, including Gmail, Google Web Search, Google Maps, and Google Docs.

There is a newer version: v20240317
Show newest version
/*
 * Copyright 2016 The Closure Compiler Authors.
 *
 * 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.
 */

'require base';
'require es6/util/makeiterator';
'require util/global';
'require util/polyfill';

/**
 * Should we expose AsyncExecutor for testing?
 * TODO(bradfordcsmith): Set this false here & arrange for it to be set to true
 * only for tests.
 * @define {boolean}
 */
$jscomp.EXPOSE_ASYNC_EXECUTOR = true;

/**
 * Should we unconditionally override a native Promise implementation with our
 * own?
 * @define {boolean}
 */
$jscomp.FORCE_POLYFILL_PROMISE = false;


$jscomp.polyfill('Promise', function(NativePromise) {
  // TODO(bradfordcsmith): Do we need to add checks for standards conformance?
  //     e.g. The version of FireFox we currently use for testing has a Promise
  //     that fails to reject attempts to fulfill it with itself, but that
  //     isn't reasonably testable here.
  if (NativePromise && !$jscomp.FORCE_POLYFILL_PROMISE) {
    return NativePromise;
  }

  /**
    * Schedules code to be executed asynchronously.
    * @constructor
    * @struct
    */
  function AsyncExecutor() {
    /**
     * Batch of functions to execute.
     *
     * Will be {@code null} initially and immediately after a batch finishes
     * executing.
     * @private {?Array}
     */
    this.batch_ = null;
  }

  /**
   * Schedule a function to execute asynchronously.
   *
   * -   The function will execute:
   *     -   After the current call stack has completed executing.
   *     -   After any functions previously scheduled using this object.
   * -   The return value will be ignored.
   * -   An exception thrown by the method will be caught and asynchronously
   *     rethrown when it cannot interrupt any other code. This class provides
   *     no way to catch such exceptions.
   * @param {function():?} f
   * @return {!AsyncExecutor} this object
   */
  AsyncExecutor.prototype.asyncExecute = function(f) {
    if (this.batch_ == null) {
      // no batch created yet, or last batch was fully executed
      this.batch_ = [];
      this.asyncExecuteBatch_();
    }
    this.batch_.push(f);
    return this;
  };

  /**
   * Schedule execution of the jobs in {@code this.batch_}.
   * @private
   */
  AsyncExecutor.prototype.asyncExecuteBatch_ = function() {
    var self = this;
    this.asyncExecuteFunction(function() { self.executeBatch_(); });
  };

  // NOTE: We want to make sure AsyncExecutor will work as expected even if
  // testing code should override setTimeout()
  /** @const */ var nativeSetTimeout = $jscomp.global['setTimeout'];

  /**
   * Schedule a function to execute asynchronously as soon as possible.
   *
   * NOTE: May be overridden for testing.
   * @package
   * @param {!Function} f
   */
  AsyncExecutor.prototype.asyncExecuteFunction = function(f) {
    nativeSetTimeout(f, 0);
  };

  /**
   * Execute scheduled jobs in a batch until all are executed or the batch
   * execution time limit has been reached.
   * @private
   */
  AsyncExecutor.prototype.executeBatch_ = function() {
    while (this.batch_ && this.batch_.length) {
      var executingBatch = this.batch_;
      // Executions scheduled while executing this batch go into a new one to
      // avoid the batch array getting too big.
      this.batch_ = [];
      for (var i = 0; i < executingBatch.length; ++i) {
        var f = executingBatch[i];
        delete executingBatch[i];  // free memory
        try {
          f();
        } catch (error) {
          this.asyncThrow_(error);
        }
      }
    }
    // All jobs finished executing, so force scheduling a new batch next
    // time asyncExecute() is called.
    this.batch_ = null;
  };

  /**
   * @private
   * @param {*} exception
   */
  AsyncExecutor.prototype.asyncThrow_ = function(exception) {
    this.asyncExecuteFunction(function() { throw exception; });
  };

  /**
   * @enum {number}
   */
  var PromiseState = {
    /** The Promise is waiting for resolution. */
    PENDING: 0,

    /** The Promise has been resolved with a fulfillment value. */
    FULFILLED: 1,

    /** The Promise has been resolved with a rejection reason. */
    REJECTED: 2
  };


  /**
   * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise
   * @param {function(
   *             function((TYPE|IThenable|Thenable|null)=),
   *             function(*=))} executor
   * @constructor
   * @extends {Promise}
   * @template TYPE
   */
  var PolyfillPromise = function(executor) {
    /** @private {PromiseState} */
    this.state_ = PromiseState.PENDING;

    /**
     * The settled result of the Promise. Immutable once set with either a
     * fulfillment value or rejection reason.
     * @private {*}
     */
    this.result_ = undefined;

    /**
     * These functions must be executed when this promise settles.
     * @private {Array}
     */
    this.onSettledCallbacks_ = [];

    var resolveAndReject = this.createResolveAndReject_();
    try {
      executor(resolveAndReject.resolve, resolveAndReject.reject);
    } catch (e) {
      resolveAndReject.reject(e);
    }
  };


  /**
   * Create a pair of functions for resolving or rejecting this Promise.
   *
   * 

After the resolve or reject function has been called once, later calls * do nothing. * @private * @return {{ * resolve: function((TYPE|IThenable|Thenable|null)=), * reject: function(*=) * }} */ PolyfillPromise.prototype.createResolveAndReject_ = function() { var thisPromise = this; var alreadyCalled = false; /** * @param {function(this:PolyfillPromise, T)} method * @return {function(T)} * @template T */ function firstCallWins(method) { return function(x) { if (!alreadyCalled) { alreadyCalled = true; method.call(thisPromise, x); } }; } return { resolve: firstCallWins(this.resolveTo_), reject: firstCallWins(this.reject_) }; }; /** * @private * @param {*} value */ PolyfillPromise.prototype.resolveTo_ = function(value) { if (value === this) { this.reject_(new TypeError('A Promise cannot resolve to itself')); } else if (value instanceof PolyfillPromise) { this.settleSameAsPromise_(/** @type {!PolyfillPromise} */ (value)); } else if (isObject(value)) { this.resolveToNonPromiseObj_(/** @type {!Object} */ (value)); } else { this.fulfill_(value); } }; /** * @private * @param {!Object} obj */ PolyfillPromise.prototype.resolveToNonPromiseObj_ = function(obj) { var thenMethod = undefined; try { thenMethod = obj.then; } catch (error) { this.reject_(error); return; } if (typeof thenMethod == 'function') { this.settleSameAsThenable_(thenMethod, /** @type {!Thenable} */ (obj)); } else { this.fulfill_(obj); } }; /** * @param {*} value anything * @return {boolean} */ function isObject(value) { switch (typeof value) { case 'object': return value != null; case 'function': return true; default: return false; } } /** * Reject this promise for the given reason. * @private * @param {*} reason * @throws {!Error} if this promise is already fulfilled or rejected. */ PolyfillPromise.prototype.reject_ = function(reason) { this.settle_(PromiseState.REJECTED, reason); }; /** * Fulfill this promise with the given value. * @private * @param {!TYPE} value * @throws {!Error} when this promise is already fulfilled or rejected. */ PolyfillPromise.prototype.fulfill_ = function(value) { this.settle_(PromiseState.FULFILLED, value); }; /** * Fulfill or reject this promise with the given value/reason. * @private * @param {!PromiseState} settledState (FULFILLED or REJECTED) * @param {*} valueOrReason * @throws {!Error} when this promise is already fulfilled or rejected. */ PolyfillPromise.prototype.settle_ = function(settledState, valueOrReason) { if (this.state_ != PromiseState.PENDING) { throw new Error( 'Cannot settle(' + settledState + ', ' + valueOrReason | '): Promise already settled in state' + this.state_); } this.state_ = settledState; this.result_ = valueOrReason; this.executeOnSettledCallbacks_(); }; PolyfillPromise.prototype.executeOnSettledCallbacks_ = function() { if (this.onSettledCallbacks_ != null) { // Allow nulls in callbacks so we can free memory var /** !Array */ callbacks = this.onSettledCallbacks_; for (var i = 0; i < callbacks.length; ++i) { (/** @type {function()} */ (callbacks[i])).call(); callbacks[i] = null; // free memory } this.onSettledCallbacks_ = null; // free memory } }; /** * All promise async execution is managed by a single executor for the * sake of efficiency. * @const {!AsyncExecutor} */ var asyncExecutor = new AsyncExecutor(); /** * Arrange to settle this promise in the same way as the given thenable. * @private * @param {!PolyfillPromise} promise */ PolyfillPromise.prototype.settleSameAsPromise_ = function(promise) { var methods = this.createResolveAndReject_(); // Calling then() would create an unnecessary extra promise. promise.callWhenSettled_(methods.resolve, methods.reject); }; /** * Arrange to settle this promise in the same way as the given thenable. * @private * @param {!function( * function((TYPE|IThenable|Thenable|null)=), * function(*=)) * } thenMethod * @param {!Thenable} thenable */ PolyfillPromise.prototype.settleSameAsThenable_ = function( thenMethod, thenable) { var methods = this.createResolveAndReject_(); // Don't trust an unknown thenable implementation not to throw exceptions. try { thenMethod.call(thenable, methods.resolve, methods.reject); } catch (error) { methods.reject(error); } }; /** @override */ PolyfillPromise.prototype.then = function(onFulfilled, onRejected) { var resolveChild; var rejectChild; var childPromise = new PolyfillPromise(function(resolve, reject) { resolveChild = resolve; rejectChild = reject; }); function createCallback(paramF, defaultF) { // The spec says to ignore non-function values for onFulfilled and // onRejected if (typeof paramF == 'function') { return function(x) { try { resolveChild(paramF(x)); } catch (error) { rejectChild(error); } }; } else { return defaultF; } } this.callWhenSettled_( createCallback(onFulfilled, resolveChild), createCallback(onRejected, rejectChild)); return childPromise; }; /** @override */ PolyfillPromise.prototype.catch = function(onRejected) { return this.then(undefined, onRejected); }; PolyfillPromise.prototype.callWhenSettled_ = function( onFulfilled, onRejected) { var thisPromise = this; function callback() { switch (thisPromise.state_) { case PromiseState.FULFILLED: onFulfilled(thisPromise.result_); break; case PromiseState.REJECTED: onRejected(thisPromise.result_); break; default: throw new Error('Unexpected state: ' + thisPromise.state_); } } if (this.onSettledCallbacks_ == null) { // we've already settled asyncExecutor.asyncExecute(callback); } else { this.onSettledCallbacks_.push(function() { asyncExecutor.asyncExecute(callback); }); } }; /** * Returns a PolyfillPromise that resolves to the given value. * *

If the type of {@code opt_value} (VALUE) is a {@code Thenable}, * the RESULT type will be {@code T}. * Otherwise, the RESULT type will be the same as VALUE. * *

NOTE: The RESULT template expression is the same as the one used for * {@code goog.IThenable.prototype.then()}. * *

TODO(bradfordcsmith): The spec actually requires {@code resolve} to * use its {@code this} value as the constructor for building the promise to * return. Right now we're always using the {@link PolyfillPromise} * constructor. * @param {VALUE=} opt_value * @return {RESULT} * @template VALUE * @template RESULT := type('PolyfillPromise', * cond(isUnknown(VALUE), unknown(), * mapunion(VALUE, (V) => * cond(isTemplatized(V) && sub(rawTypeOf(V), 'IThenable'), * templateTypeOf(V, 0), * cond(sub(V, 'Thenable'), * unknown(), * V))))) * =: */ PolyfillPromise.resolve = function(opt_value) { if (opt_value instanceof PolyfillPromise) { return opt_value; } else { return new PolyfillPromise(function(resolve, reject) { resolve(opt_value); }); } }; /** * @param {*=} opt_reason * @return {!Promise} */ PolyfillPromise.reject = function(opt_reason) { return new PolyfillPromise(function(resolve, reject) { reject(opt_reason); }); }; /** * @param {!Array<(TYPE|!IThenable)>} thenablesOrValues * @return {!Promise} A Promise that receives the result of the * first Promise (or Promise-like) input to settle immediately after it * settles. * @template TYPE */ PolyfillPromise.race = function(thenablesOrValues) { return new PolyfillPromise(function(resolve, reject) { var iterator = $jscomp.makeIterator(thenablesOrValues); for (var /** !IIterableResult<*> */ iterRec = iterator.next(); !iterRec.done; iterRec = iterator.next()) { // Using Promise.resolve() allows us to treat all elements the same way. // NOTE: Promise.resolve(promise) always returns the argument unchanged. // Using .callWhenSettled_() instead of .then() avoids creating an // unnecessary extra promise. PolyfillPromise.resolve(iterRec.value) .callWhenSettled_(resolve, reject); } }); }; /** * @template T * @param {!Array>|!Iterable>} thenablesOrValues * @return {!Promise>} */ PolyfillPromise.all = function(thenablesOrValues) { var iterator = $jscomp.makeIterator(thenablesOrValues); var /** !IIterableResult<*> */ iterRec = iterator.next(); if (iterRec.done) { return PolyfillPromise.resolve([]); } else { return new PolyfillPromise(function(resolveAll, rejectAll) { var resultsArray = []; var unresolvedCount = 0; function onFulfilled(i) { return function(ithResult) { resultsArray[i] = ithResult; unresolvedCount--; if (unresolvedCount == 0) { resolveAll(resultsArray); } }; } do { resultsArray.push(undefined); unresolvedCount++; // Using Promise.resolve() allows us to treat all elements the same // way. // NOTE: Promise.resolve(promise) always returns the argument // unchanged. Using .callWhenSettled_() instead of .then() avoids // creating an unnecessary extra promise. PolyfillPromise.resolve(iterRec.value) .callWhenSettled_( onFulfilled(resultsArray.length - 1), rejectAll); iterRec = iterator.next(); } while (!iterRec.done); }); } }; if ($jscomp.EXPOSE_ASYNC_EXECUTOR) { // expose AsyncExecutor so it can be tested independently. PolyfillPromise['$jscomp$new$AsyncExecutor'] = function() { return new AsyncExecutor(); }; } return PolyfillPromise; }, 'es6-impl', 'es3');





© 2015 - 2024 Weber Informatics LLC | Privacy Policy