1'use strict';
2
3var asap = require('asap/raw');
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._deferredState = 0;
62  this._state = 0;
63  this._value = null;
64  this._deferreds = null;
65  if (fn === noop) return;
66  doResolve(fn, this);
67}
68Promise._onHandle = null;
69Promise._onReject = null;
70Promise._noop = 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._state === 3) {
90    self = self._value;
91  }
92  if (Promise._onHandle) {
93    Promise._onHandle(self);
94  }
95  if (self._state === 0) {
96    if (self._deferredState === 0) {
97      self._deferredState = 1;
98      self._deferreds = deferred;
99      return;
100    }
101    if (self._deferredState === 1) {
102      self._deferredState = 2;
103      self._deferreds = [self._deferreds, deferred];
104      return;
105    }
106    self._deferreds.push(deferred);
107    return;
108  }
109  handleResolved(self, deferred);
110}
111
112function handleResolved(self, deferred) {
113  asap(function() {
114    var cb = self._state === 1 ? deferred.onFulfilled : deferred.onRejected;
115    if (cb === null) {
116      if (self._state === 1) {
117        resolve(deferred.promise, self._value);
118      } else {
119        reject(deferred.promise, self._value);
120      }
121      return;
122    }
123    var ret = tryCallOne(cb, self._value);
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._state = 3;
152      self._value = newValue;
153      finale(self);
154      return;
155    } else if (typeof then === 'function') {
156      doResolve(then.bind(newValue), self);
157      return;
158    }
159  }
160  self._state = 1;
161  self._value = newValue;
162  finale(self);
163}
164
165function reject(self, newValue) {
166  self._state = 2;
167  self._value = newValue;
168  if (Promise._onReject) {
169    Promise._onReject(self, newValue);
170  }
171  finale(self);
172}
173function finale(self) {
174  if (self._deferredState === 1) {
175    handle(self, self._deferreds);
176    self._deferreds = null;
177  }
178  if (self._deferredState === 2) {
179    for (var i = 0; i < self._deferreds.length; i++) {
180      handle(self, self._deferreds[i]);
181    }
182    self._deferreds = 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