• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1import {
2  objectOrFunction,
3  isFunction
4} from './utils';
5
6import {
7  asap
8} from './asap';
9
10import originalThen from './then';
11import originalResolve from './promise/resolve';
12
13export const PROMISE_ID = Math.random().toString(36).substring(2);
14
15function noop() {}
16
17const PENDING   = void 0;
18const FULFILLED = 1;
19const REJECTED  = 2;
20
21function selfFulfillment() {
22  return new TypeError("You cannot resolve a promise with itself");
23}
24
25function cannotReturnOwn() {
26  return new TypeError('A promises callback cannot return that same promise.');
27}
28
29function tryThen(then, value, fulfillmentHandler, rejectionHandler) {
30  try {
31    then.call(value, fulfillmentHandler, rejectionHandler);
32  } catch(e) {
33    return e;
34  }
35}
36
37function handleForeignThenable(promise, thenable, then) {
38   asap(promise => {
39    let sealed = false;
40    let error = tryThen(then, thenable, value => {
41      if (sealed) { return; }
42      sealed = true;
43      if (thenable !== value) {
44        resolve(promise, value);
45      } else {
46        fulfill(promise, value);
47      }
48    }, reason => {
49      if (sealed) { return; }
50      sealed = true;
51
52      reject(promise, reason);
53    }, 'Settle: ' + (promise._label || ' unknown promise'));
54
55    if (!sealed && error) {
56      sealed = true;
57      reject(promise, error);
58    }
59  }, promise);
60}
61
62function handleOwnThenable(promise, thenable) {
63  if (thenable._state === FULFILLED) {
64    fulfill(promise, thenable._result);
65  } else if (thenable._state === REJECTED) {
66    reject(promise, thenable._result);
67  } else {
68    subscribe(thenable, undefined, value  => resolve(promise, value),
69                                   reason => reject(promise, reason))
70  }
71}
72
73function handleMaybeThenable(promise, maybeThenable, then) {
74  if (maybeThenable.constructor === promise.constructor &&
75      then === originalThen &&
76      maybeThenable.constructor.resolve === originalResolve) {
77    handleOwnThenable(promise, maybeThenable);
78  } else {
79    if (then === undefined) {
80      fulfill(promise, maybeThenable);
81    } else if (isFunction(then)) {
82      handleForeignThenable(promise, maybeThenable, then);
83    } else {
84      fulfill(promise, maybeThenable);
85    }
86  }
87}
88
89function resolve(promise, value) {
90  if (promise === value) {
91    reject(promise, selfFulfillment());
92  } else if (objectOrFunction(value)) {
93    let then;
94    try {
95      then = value.then;
96    } catch (error) {
97      reject(promise, error);
98      return;
99    }
100    handleMaybeThenable(promise, value, then);
101  } else {
102    fulfill(promise, value);
103  }
104}
105
106function publishRejection(promise) {
107  if (promise._onerror) {
108    promise._onerror(promise._result);
109  }
110
111  publish(promise);
112}
113
114function fulfill(promise, value) {
115  if (promise._state !== PENDING) { return; }
116
117  promise._result = value;
118  promise._state = FULFILLED;
119
120  if (promise._subscribers.length !== 0) {
121    asap(publish, promise);
122  }
123}
124
125function reject(promise, reason) {
126  if (promise._state !== PENDING) { return; }
127  promise._state = REJECTED;
128  promise._result = reason;
129
130  asap(publishRejection, promise);
131}
132
133function subscribe(parent, child, onFulfillment, onRejection) {
134  let { _subscribers } = parent;
135  let { length } = _subscribers;
136
137  parent._onerror = null;
138
139  _subscribers[length] = child;
140  _subscribers[length + FULFILLED] = onFulfillment;
141  _subscribers[length + REJECTED]  = onRejection;
142
143  if (length === 0 && parent._state) {
144    asap(publish, parent);
145  }
146}
147
148function publish(promise) {
149  let subscribers = promise._subscribers;
150  let settled = promise._state;
151
152  if (subscribers.length === 0) { return; }
153
154  let child, callback, detail = promise._result;
155
156  for (let i = 0; i < subscribers.length; i += 3) {
157    child = subscribers[i];
158    callback = subscribers[i + settled];
159
160    if (child) {
161      invokeCallback(settled, child, callback, detail);
162    } else {
163      callback(detail);
164    }
165  }
166
167  promise._subscribers.length = 0;
168}
169
170function invokeCallback(settled, promise, callback, detail) {
171  let hasCallback = isFunction(callback),
172      value, error, succeeded = true;
173
174  if (hasCallback) {
175    try {
176      value = callback(detail);
177    } catch (e) {
178      succeeded = false;
179      error = e;
180    }
181
182    if (promise === value) {
183      reject(promise, cannotReturnOwn());
184      return;
185    }
186  } else {
187    value = detail;
188  }
189
190  if (promise._state !== PENDING) {
191    // noop
192  } else if (hasCallback && succeeded) {
193    resolve(promise, value);
194  } else if (succeeded === false) {
195    reject(promise, error);
196  } else if (settled === FULFILLED) {
197    fulfill(promise, value);
198  } else if (settled === REJECTED) {
199    reject(promise, value);
200  }
201}
202
203function initializePromise(promise, resolver) {
204  try {
205    resolver(function resolvePromise(value){
206      resolve(promise, value);
207    }, function rejectPromise(reason) {
208      reject(promise, reason);
209    });
210  } catch(e) {
211    reject(promise, e);
212  }
213}
214
215let id = 0;
216function nextId() {
217  return id++;
218}
219
220function makePromise(promise) {
221  promise[PROMISE_ID] = id++;
222  promise._state = undefined;
223  promise._result = undefined;
224  promise._subscribers = [];
225}
226
227export {
228  nextId,
229  makePromise,
230  noop,
231  resolve,
232  reject,
233  fulfill,
234  subscribe,
235  publish,
236  publishRejection,
237  initializePromise,
238  invokeCallback,
239  FULFILLED,
240  REJECTED,
241  PENDING,
242  handleMaybeThenable
243};
244