JS Simple Promise

简单 Promise 实现,未测试。


参考:

  1. https://github.com/nfroidure/Promise
  2. https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Promise
// untested
// @ref https://github.com/nfroidure/Promise

const STATES = {
  // pending: initial state, neither fulfilled nor rejected.
  // fulfilled: meaning that the operation completed successfully.
  // rejected: meaning that the operation failed.
  get PENDING () { return 0 },
  get FULFILLED () { return 1 },
  get REJECTED () { return 2 },
  get DISPOSED () { return 3 }
};

module.exports = (function () {
  'use strict';

  const $ = function (f) {
    // Another version of private
    const self = { };

    // Init the state
    self.status =  STATES.PENDING;

    this.__defineGetter__('status', function () {
      return self.status;
    });

    // Prepare callbacks registration
    self.fulfilledCallbacks = [];
    self.rejectedCallbacks = [];
    self.progressCallbacks = [];

    self.value = undefined;
    self.reason = undefined;
    this.__defineGetter__('value', function () {
      return self.value;
    });
    this.__defineGetter__('reason', function () {
      return self.reason;
    });

    /* private wrap */
    self._onFulfilled = __onFulfilled.bind(this, self);
    self._onRejected = __onRejected.bind(this, self);
    self._onProgress = __onProgress.bind(this, self);
    self._dispose = __dispose.bind(this, self);

    /* public wrap */
    this.then = __then.bind(this, self);

    self.dispose = f(self._onFulfilled, self._onRejected, self._onProgress);
  };

  /* public */
  function __then (self, onFulfilled, onRejected, onProgress) {
    let thenFulfill;
    let thenReject;
    let thenProgress;
    let thenDispose = self._dispose;

    let thenPromise = new $((resolve, reject, progress) => {
      thenFulfill = resolve;
      thenReject = reject;
      thenProgress = progress;
      return thenDispose;
    });

    const t = thenPromise.status;

    let fulfill = function () {
      let returnValue;
      if (STATES.PENDING !== thenPromise.status) return;
      if (onFulfilled) returnValue = onFulfilled(self.value);
      if (returnValue instanceof $)
        returnValue.then(thenFulfill, thenReject, thenProgress);
      else
        thenFulfill(returnValue);
    };

    let reject = function () {
      let returnValue;
      if (STATES.PENDING !== thenPromise.status)  return;
      if (onRejected) returnValue = onRejected(self.reason);
      if (returnValue instanceof $)
        returnValue.then(thenFulfill, thenReject, thenProgress);
      else
        thenReject && thenReject(
          returnValue && returnValue.reason ?
          returnValue.reason :
          returnValue
        );
    };

    switch (self.status) {
      case STATES.PENDING:
        self.fulfilledCallbacks.push(fulfill);
        self.rejectedCallbacks.push(reject);
        onProgress && self.progressCallbacks.push(onProgress);
        break;

      case STATES.FULFILLED:
        process.nextTick(fulfill);
        break;

      case STATES.REJECTED:
        process.nextTick(reject);
        break;

      case STATES.DISPOSED:
    }

    return thenPromise;
  };

  /* private */
  function __dispose (self) {
    if (self.status ===  STATES.PENDING) {
      self.status =  STATES.DISPOSED;
      
      self.dispose && self.dispose();
      
      self.fulfilledCallbacks = [];
      self.rejectedCallbacks = [];
      self.progressCallbacks = [];
      
      self.value = undefined;
      self.reason = undefined;
    }
  }

  function __onFulfilled (self, value) {
    if ( STATES.PENDING !== self.status) return;

    self.status =  STATES.FULFILLED;
    self.value = value;

    while (self.fulfilledCallbacks.length !== 0
      && self.status !==  STATES.REJECTED
      && self.status !==  STATES.DISPOSED)
        self.fulfilledCallbacks.shift()(self.value)

    self.rejectedCallbacks = [];
    self.progressCallbacks = [];
  }

  function __onRejected (self, reason) {
    if ( STATES.DISPOSED === promise.status) return;

    self.status =  STATES.REJECTED;
    self.reason = reason;

    while (self.rejectedCallbacks.length
      && self.status !==  STATES.REJECTED)
        self.rejectedCallbacks.shift()(self.reason);

    promise.fulfilledCallbacks = [];
    promise.progressCallbacks = [];
  }

  function __onProgress (self, value) {
    // called by the resolver when the fullfill progress
    if ( STATES.DISPOSED === self.status) return;

    for (let i = 0; i < self.progressCallbacks.length; i++)
      promise.progressCallbacks[i](value);
  }


  /* public static */

  /* Composition */

  /**
   * Creates a promise fullfilled when 'n' of the promises given are
   */  
  $.some = function (n, ...promises) {
    if ((!n) || (n < 0))
      throw Error('n must be superior or equal to 0');

    if (promises.length < 1) 
      throw Error('the method - some wait a least 2 arguments.');

    n = n > promises.length && promises.length || n;
    
    let status = 0;
    let rejected = 0;
    let returnValues = new Array(promises.length);
    let returnReasons = new Array(promises.length);
    return new $(function (resolve, reject) {
      const promiseDispose = function () {
        promises.forEach( p =>  STATES.PENDING === p.status && p._dispose() );
      };
      const promiseFulfilled = function (promise, index) {
        return value => {
          if(status < promises.length) {
            returnValues[index] = value;
            status++;
            if (status == n){
              promiseDispose();
              resolve(returnValues);
            }
          }
        };
      };
      const promiseRejected = function (promise, index) {
        return reason => {
          rejected++;
          returnReasons[index] = reason;
          if (status + rejected == promises.length) {
            promiseDispose();
            reject(returnReasons);
          }
        };
      };
      promises.forEach((promise, index) => {
        promise.then(promiseFulfilled(promise, index),
        promiseRejected(promise, index));
      });
      return promiseDispose;
    });
  };

  /**
   * Creates a promise fullfilled when each promises given are
   */
  $.all = $.every = function (promises) {
    if (promises.length < 2)
      throw Error('the method - all must have at least 2 Promises as arguments.');

    return $.some.apply(this, [promises.length]
      .concat(Array.prototype.slice.call(promises, 0)));
  };

  /**
   * Creates a promise fullfilled when one of the given promises is
   */
  $.any = function (promises) {
    return $.some.apply(
      this, [1]
      .concat(Array.prototype.slice.call(promises,0))
    ).then(results => {
      for (let i = 0; i < results.length; i++)
        if (results[i] !== undefined)
          return $.fullfill(results[i]);
    }, errors => {
      return $.reject(errors);
    });
  };

  /**
   * Chain promises to resolve sequentially
   */
  $.seq = function (promises) {
    if (promises.length < 2)
      throw Error('the method - seq must have at least 2 Promises as arguments.');

    let lastPromise = promises[0];
    for (let i = 1; i < promises.length; i++) {
      lastPromise.then(() => arguments[i]);
      lastPromise = promises;
    }
    return promises[0];
  }

  /* Generators */

  /**
   * Creates a promise fullfilled after 'time' milliseconds
   */
  $.elapsed = function (time, progressPace) {
    return new $(function (resolve, reject, progress) {
      const timestamp = Date.now();
      const timeout = setTimeout(() => {
        progress(0);
        resolve(Date.now() - timestamp);
      }, time);

      if (progressPace) {
        let n = Math.floor(time/progressPace);
        let progressTimeout;
        const timeoutProgress = () => {
          progress(n);
          if(--n > 0) {
            progressTimeout = setTimeout(timeoutProgress, progressPace);
          }
        };
        progressTimeout = setTimeout(timeoutProgress, 0);
      }
      return function() {
        clearTimeout(timeout);
        clearTimeout(progressTimeout);
      };
    });
  };

  /**
   * Creates a never fullfilled promise
   */
  $.dumb = $.never = function () {
    return new $(function (resolve, reject) { });
  };

  /**
   * Creates a systematically fullfilled promise
   */
  $.sure = $.fullfill = function (value, isAsync) {
    return new $(function (resolve, reject) {
      if (isAsync) {
        process.nextTick(() => resolve(value));
      } else {
        resolve(value);
      }
    });
  };

  /**
   * Creates a systematically rejected promise
   */
  $.reject = function (reason, isAsync) {
    return new $(function(resolve, reject) {
      if (isAsync) {
        process.nextTick(() => reject(reason));
      } else {
        reject(reason);
      }
    });
  };

  return $;
})();

作者: YanWen

Web 开发者

发表评论

Fill in your details below or click an icon to log in:

WordPress.com 徽标

You are commenting using your WordPress.com account. Log Out /  更改 )

Google photo

You are commenting using your Google account. Log Out /  更改 )

Twitter picture

You are commenting using your Twitter account. Log Out /  更改 )

Facebook photo

You are commenting using your Facebook account. Log Out /  更改 )

Connecting to %s