UNPKG

6.13 kBJavaScriptView Raw
1import promiseFinally from './finally';
2import allSettled from './allSettled';
3
4// Store setTimeout reference so promise-polyfill will be unaffected by
5// other code modifying setTimeout (like sinon.useFakeTimers())
6var setTimeoutFunc = setTimeout;
7// @ts-ignore
8var setImmediateFunc = typeof setImmediate !== 'undefined' ? setImmediate : null;
9
10function isArray(x) {
11 return Boolean(x && typeof x.length !== 'undefined');
12}
13
14function noop() {}
15
16// Polyfill for Function.prototype.bind
17function bind(fn, thisArg) {
18 return function() {
19 fn.apply(thisArg, arguments);
20 };
21}
22
23/**
24 * @constructor
25 * @param {Function} fn
26 */
27function Promise(fn) {
28 if (!(this instanceof Promise))
29 throw new TypeError('Promises must be constructed via new');
30 if (typeof fn !== 'function') throw new TypeError('not a function');
31 /** @type {!number} */
32 this._state = 0;
33 /** @type {!boolean} */
34 this._handled = false;
35 /** @type {Promise|undefined} */
36 this._value = undefined;
37 /** @type {!Array<!Function>} */
38 this._deferreds = [];
39
40 doResolve(fn, this);
41}
42
43function handle(self, deferred) {
44 while (self._state === 3) {
45 self = self._value;
46 }
47 if (self._state === 0) {
48 self._deferreds.push(deferred);
49 return;
50 }
51 self._handled = true;
52 Promise._immediateFn(function() {
53 var cb = self._state === 1 ? deferred.onFulfilled : deferred.onRejected;
54 if (cb === null) {
55 (self._state === 1 ? resolve : reject)(deferred.promise, self._value);
56 return;
57 }
58 var ret;
59 try {
60 ret = cb(self._value);
61 } catch (e) {
62 reject(deferred.promise, e);
63 return;
64 }
65 resolve(deferred.promise, ret);
66 });
67}
68
69function resolve(self, newValue) {
70 try {
71 // Promise Resolution Procedure: https://github.com/promises-aplus/promises-spec#the-promise-resolution-procedure
72 if (newValue === self)
73 throw new TypeError('A promise cannot be resolved with itself.');
74 if (
75 newValue &&
76 (typeof newValue === 'object' || typeof newValue === 'function')
77 ) {
78 var then = newValue.then;
79 if (newValue instanceof Promise) {
80 self._state = 3;
81 self._value = newValue;
82 finale(self);
83 return;
84 } else if (typeof then === 'function') {
85 doResolve(bind(then, newValue), self);
86 return;
87 }
88 }
89 self._state = 1;
90 self._value = newValue;
91 finale(self);
92 } catch (e) {
93 reject(self, e);
94 }
95}
96
97function reject(self, newValue) {
98 self._state = 2;
99 self._value = newValue;
100 finale(self);
101}
102
103function finale(self) {
104 if (self._state === 2 && self._deferreds.length === 0) {
105 Promise._immediateFn(function() {
106 if (!self._handled) {
107 Promise._unhandledRejectionFn(self._value);
108 }
109 });
110 }
111
112 for (var i = 0, len = self._deferreds.length; i < len; i++) {
113 handle(self, self._deferreds[i]);
114 }
115 self._deferreds = null;
116}
117
118/**
119 * @constructor
120 */
121function Handler(onFulfilled, onRejected, promise) {
122 this.onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : null;
123 this.onRejected = typeof onRejected === 'function' ? onRejected : null;
124 this.promise = promise;
125}
126
127/**
128 * Take a potentially misbehaving resolver function and make sure
129 * onFulfilled and onRejected are only called once.
130 *
131 * Makes no guarantees about asynchrony.
132 */
133function doResolve(fn, self) {
134 var done = false;
135 try {
136 fn(
137 function(value) {
138 if (done) return;
139 done = true;
140 resolve(self, value);
141 },
142 function(reason) {
143 if (done) return;
144 done = true;
145 reject(self, reason);
146 }
147 );
148 } catch (ex) {
149 if (done) return;
150 done = true;
151 reject(self, ex);
152 }
153}
154
155Promise.prototype['catch'] = function(onRejected) {
156 return this.then(null, onRejected);
157};
158
159Promise.prototype.then = function(onFulfilled, onRejected) {
160 // @ts-ignore
161 var prom = new this.constructor(noop);
162
163 handle(this, new Handler(onFulfilled, onRejected, prom));
164 return prom;
165};
166
167Promise.prototype['finally'] = promiseFinally;
168
169Promise.all = function(arr) {
170 return new Promise(function(resolve, reject) {
171 if (!isArray(arr)) {
172 return reject(new TypeError('Promise.all accepts an array'));
173 }
174
175 var args = Array.prototype.slice.call(arr);
176 if (args.length === 0) return resolve([]);
177 var remaining = args.length;
178
179 function res(i, val) {
180 try {
181 if (val && (typeof val === 'object' || typeof val === 'function')) {
182 var then = val.then;
183 if (typeof then === 'function') {
184 then.call(
185 val,
186 function(val) {
187 res(i, val);
188 },
189 reject
190 );
191 return;
192 }
193 }
194 args[i] = val;
195 if (--remaining === 0) {
196 resolve(args);
197 }
198 } catch (ex) {
199 reject(ex);
200 }
201 }
202
203 for (var i = 0; i < args.length; i++) {
204 res(i, args[i]);
205 }
206 });
207};
208
209Promise.allSettled = allSettled;
210
211Promise.resolve = function(value) {
212 if (value && typeof value === 'object' && value.constructor === Promise) {
213 return value;
214 }
215
216 return new Promise(function(resolve) {
217 resolve(value);
218 });
219};
220
221Promise.reject = function(value) {
222 return new Promise(function(resolve, reject) {
223 reject(value);
224 });
225};
226
227Promise.race = function(arr) {
228 return new Promise(function(resolve, reject) {
229 if (!isArray(arr)) {
230 return reject(new TypeError('Promise.race accepts an array'));
231 }
232
233 for (var i = 0, len = arr.length; i < len; i++) {
234 Promise.resolve(arr[i]).then(resolve, reject);
235 }
236 });
237};
238
239// Use polyfill for setImmediate for performance gains
240Promise._immediateFn =
241 // @ts-ignore
242 (typeof setImmediateFunc === 'function' &&
243 function(fn) {
244 // @ts-ignore
245 setImmediateFunc(fn);
246 }) ||
247 function(fn) {
248 setTimeoutFunc(fn, 0);
249 };
250
251Promise._unhandledRejectionFn = function _unhandledRejectionFn(err) {
252 if (typeof console !== 'undefined' && console) {
253 console.warn('Possible Unhandled Promise Rejection:', err); // eslint-disable-line no-console
254 }
255};
256
257export default Promise;