15 May 2017
// https://promisesaplus.com
const PENDING = 0
const FULFILLED = 1
const REJECTED = 2

class Promise {
  constructor (executor) {
    this._deferreds = []
    this._state = PENDING
    this._value = null
    this._reason = null
    executor(value => { this._resolve(value) },
             reason => { this._reject(reason) })
  }

  _handle (deferred) {
    if (this._state === PENDING) {
      this._deferreds.push(deferred)
    } else {
      if (typeof(this._state === FULFILLED
        ? deferred.onFulfilled : deferred.onRejected) === 'function') {
        try {
          // https://promisesaplus.com/#point-41
          deferred.resolve(this._state === FULFILLED 
                           ? deferred.onFulfilled(this._value)
                           : deferred.onRejected(this._reason))
        } catch (e) {
          // https://promisesaplus.com/#point-42
          deferred.reject(e)
        }
      } else {
        // https://promisesaplus.com/#point-43 & https://promisesaplus.com/#point-44
        this._state === FULFILLED
          ? deferred.resolve(this._value) : deferred.reject(this._reason)
      }
    }
  }

  _resolve (value) {
    // https://promisesaplus.com/#point-17
    if (this._state !== PENDING) return
    if (value && value.constructor === this.constructor) {
      // https://promisesaplus.com/#point-49
      value.then(value => { this._resolve(value) },
                 reason => { this._reject(reason) })
    } else {
      this._state = FULFILLED
      this._value = value
      this._done()
    }
  }

  _reject (reason) {
    // https://promisesaplus.com/#point-14
    if (this._state !== PENDING) return
    this._state = REJECTED
    this._reason = reason
    this._done()
  }

  _done () {
    // console.log(this._state, this._reason || this._value, this.deffereds)
    window.setTimeout(_ => { 
      this._deferreds.forEach(deferred => { this._handle(deferred) })
    }, 0)
  }

  then (onFulfilled, onRejected) {
    // https://promisesaplus.com/#point-40
    return new this.constructor(
      (resolve, reject) => { 
        this._handle({
          onFulfilled: onFulfilled,
          onRejected: onRejected,
          resolve: resolve,
          reject: reject
        })
    })
  }

  catch (onRejected) {
    return this.then(undefined, onRejected)
  }

  finally (cb) {
    if (typeof cb === 'function') {
      return this.then(value => Promise.resolve(cb()).then(_ => value),
                       reason => Promise.resolve(cb()).then(_ => { throw reason }))
    }
  }

  // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/resolve
  static resolve (value) {
    return new Promise((resolve, reject) => {
      resolve(value)
    })
  }

  // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/reject
  static reject (reason) {
    return new Promise((resolve, reject) => {
      reject(reason)
    })
  }

  // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/race
  static race (iterable) {
    return new Promise((resolve, reject) => {
      iterable.forEach(value => Promise.resolve(value).then(resolve, reject))
    })
  }

  // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/all
  static all (iterable) {
    return new Promise((resolve, reject) => {
      let values = []
      for (let i = 0, len = iterable.length; i < len; ++i) {
        iterable[i].then(value => {
          values.push(value)
          i >= len - 1 && resolve(values)
        }, reason => {
          reject(reason)
        })
      }
    })
  }
}

Test