1'use strict';
2
3
4
5function noop() {}
6
7// States:
8//
9// 0 - pending
10// 1 - fulfilled with _value
11// 2 - rejected with _value
12// 3 - adopted the state of another promise, _value
13//
14// once the state is no longer pending (0) it is immutable
15
16// All `_` prefixed properties will be reduced to `_{random number}`
17// at build time to obfuscate them and discourage their use.
18// We don't use symbols or Object.defineProperty to fully hide them
19// because the performance isn't good enough.
20
21
22// to avoid using try/catch inside critical functions, we
23// extract them to here.
24var LAST_ERROR = null;
25var IS_ERROR = {};
26function getThen(obj) {
27  try {
28    return obj.then;
29  } catch (ex) {
30    LAST_ERROR = ex;
31    return IS_ERROR;
32  }
33}
34
35function tryCallOne(fn, a) {
36  try {
37    return fn(a);
38  } catch (ex) {
39    LAST_ERROR = ex;
40    return IS_ERROR;
41  }
42}
43function tryCallTwo(fn, a, b) {
44  try {
45    fn(a, b);
46  } catch (ex) {
47    LAST_ERROR = ex;
48    return IS_ERROR;
49  }
50}
51
52module.exports = Promise;
53
54function Promise(fn) {
55  if (typeof this !== 'object') {
56    throw new TypeError('Promises must be constructed via new');
57  }
58  if (typeof fn !== 'function') {
59    throw new TypeError('Promise constructor\'s argument is not a function');
60  }
61  this._40 = 0;
62  this._65 = 0;
63  this._55 = null;
64  this._72 = null;
65  if (fn === noop) return;
66  doResolve(fn, this);
67}
68Promise._37 = null;
69Promise._87 = null;
70Promise._61 = noop;
71
72Promise.prototype.then = function(onFulfilled, onRejected) {
73  if (this.constructor !== Promise) {
74    return safeThen(this, onFulfilled, onRejected);
75  }
76  var res = new Promise(noop);
77  handle(this, new Handler(onFulfilled, onRejected, res));
78  return res;
79};
80
81function safeThen(self, onFulfilled, onRejected) {
82  return new self.constructor(function (resolve, reject) {
83    var res = new Promise(noop);
84    res.then(resolve, reject);
85    handle(self, new Handler(onFulfilled, onRejected, res));
86  });
87}
88function handle(self, deferred) {
89  while (self._65 === 3) {
90    self = self._55;
91  }
92  if (Promise._37) {
93    Promise._37(self);
94  }
95  if (self._65 === 0) {
96    if (self._40 === 0) {
97      self._40 = 1;
98      self._72 = deferred;
99      return;
100    }
101    if (self._40 === 1) {
102      self._40 = 2;
103      self._72 = [self._72, deferred];
104      return;
105    }
106    self._72.push(deferred);
107    return;
108  }
109  handleResolved(self, deferred);
110}
111
112function handleResolved(self, deferred) {
113  setImmediate(function() {
114    var cb = self._65 === 1 ? deferred.onFulfilled : deferred.onRejected;
115    if (cb === null) {
116      if (self._65 === 1) {
117        resolve(deferred.promise, self._55);
118      } else {
119        reject(deferred.promise, self._55);
120      }
121      return;
122    }
123    var ret = tryCallOne(cb, self._55);
124    if (ret === IS_ERROR) {
125      reject(deferred.promise, LAST_ERROR);
126    } else {
127      resolve(deferred.promise, ret);
128    }
129  });
130}
131function resolve(self, newValue) {
132  // Promise Resolution Procedure: https://github.com/promises-aplus/promises-spec#the-promise-resolution-procedure
133  if (newValue === self) {
134    return reject(
135      self,
136      new TypeError('A promise cannot be resolved with itself.')
137    );
138  }
139  if (
140    newValue &&
141    (typeof newValue === 'object' || typeof newValue === 'function')
142  ) {
143    var then = getThen(newValue);
144    if (then === IS_ERROR) {
145      return reject(self, LAST_ERROR);
146    }
147    if (
148      then === self.then &&
149      newValue instanceof Promise
150    ) {
151      self._65 = 3;
152      self._55 = newValue;
153      finale(self);
154      return;
155    } else if (typeof then === 'function') {
156      doResolve(then.bind(newValue), self);
157      return;
158    }
159  }
160  self._65 = 1;
161  self._55 = newValue;
162  finale(self);
163}
164
165function reject(self, newValue) {
166  self._65 = 2;
167  self._55 = newValue;
168  if (Promise._87) {
169    Promise._87(self, newValue);
170  }
171  finale(self);
172}
173function finale(self) {
174  if (self._40 === 1) {
175    handle(self, self._72);
176    self._72 = null;
177  }
178  if (self._40 === 2) {
179    for (var i = 0; i < self._72.length; i++) {
180      handle(self, self._72[i]);
181    }
182    self._72 = null;
183  }
184}
185
186function Handler(onFulfilled, onRejected, promise){
187  this.onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : null;
188  this.onRejected = typeof onRejected === 'function' ? onRejected : null;
189  this.promise = promise;
190}
191
192/**
193 * Take a potentially misbehaving resolver function and make sure
194 * onFulfilled and onRejected are only called once.
195 *
196 * Makes no guarantees about asynchrony.
197 */
198function doResolve(fn, promise) {
199  var done = false;
200  var res = tryCallTwo(fn, function (value) {
201    if (done) return;
202    done = true;
203    resolve(promise, value);
204  }, function (reason) {
205    if (done) return;
206    done = true;
207    reject(promise, reason);
208  });
209  if (!done && res === IS_ERROR) {
210    done = true;
211    reject(promise, LAST_ERROR);
212  }
213}
214