1 | /*!
|
2 | Embeddable Minimum Strictly-Compliant Promises/A+ 1.1.1 Thenable
|
3 | Copyright (c) 2013-2014 Ralf S. Engelschall (http://engelschall.com)
|
4 | Licensed under The MIT License (http://opensource.org/licenses/MIT)
|
5 | */
|
6 |
|
7 | /* promise states [Promises/A+ 2.1] */
|
8 | var STATE_PENDING = 0; /* [Promises/A+ 2.1.1] */
|
9 | var STATE_FULFILLED = 1; /* [Promises/A+ 2.1.2] */
|
10 | var STATE_REJECTED = 2; /* [Promises/A+ 2.1.3] */
|
11 |
|
12 | /* promise object constructor */
|
13 | var api = function( executor ){
|
14 | /* optionally support non-constructor/plain-function call */
|
15 | if( !(this instanceof api) )
|
16 | return new api( executor );
|
17 |
|
18 | /* initialize object */
|
19 | this.id = 'Thenable/1.0.7';
|
20 | this.state = STATE_PENDING; /* initial state */
|
21 | this.fulfillValue = undefined; /* initial value */ /* [Promises/A+ 1.3, 2.1.2.2] */
|
22 | this.rejectReason = undefined; /* initial reason */ /* [Promises/A+ 1.5, 2.1.3.2] */
|
23 | this.onFulfilled = []; /* initial handlers */
|
24 | this.onRejected = []; /* initial handlers */
|
25 |
|
26 | /* provide optional information-hiding proxy */
|
27 | this.proxy = {
|
28 | then: this.then.bind( this )
|
29 | };
|
30 |
|
31 | /* support optional executor function */
|
32 | if( typeof executor === 'function' )
|
33 | executor.call( this, this.fulfill.bind( this ), this.reject.bind( this ) );
|
34 | };
|
35 |
|
36 | /* promise API methods */
|
37 | api.prototype = {
|
38 | /* promise resolving methods */
|
39 | fulfill: function( value ){ return deliver( this, STATE_FULFILLED, 'fulfillValue', value ); },
|
40 | reject: function( value ){ return deliver( this, STATE_REJECTED, 'rejectReason', value ); },
|
41 |
|
42 | /* "The then Method" [Promises/A+ 1.1, 1.2, 2.2] */
|
43 | then: function( onFulfilled, onRejected ){
|
44 | var curr = this;
|
45 | var next = new api(); /* [Promises/A+ 2.2.7] */
|
46 | curr.onFulfilled.push(
|
47 | resolver( onFulfilled, next, 'fulfill' ) ); /* [Promises/A+ 2.2.2/2.2.6] */
|
48 | curr.onRejected.push(
|
49 | resolver( onRejected, next, 'reject' ) ); /* [Promises/A+ 2.2.3/2.2.6] */
|
50 | execute( curr );
|
51 | return next.proxy; /* [Promises/A+ 2.2.7, 3.3] */
|
52 | }
|
53 | };
|
54 |
|
55 | /* deliver an action */
|
56 | var deliver = function( curr, state, name, value ){
|
57 | if( curr.state === STATE_PENDING ){
|
58 | curr.state = state; /* [Promises/A+ 2.1.2.1, 2.1.3.1] */
|
59 | curr[ name ] = value; /* [Promises/A+ 2.1.2.2, 2.1.3.2] */
|
60 | execute( curr );
|
61 | }
|
62 | return curr;
|
63 | };
|
64 |
|
65 | /* execute all handlers */
|
66 | var execute = function( curr ){
|
67 | if( curr.state === STATE_FULFILLED )
|
68 | execute_handlers( curr, 'onFulfilled', curr.fulfillValue );
|
69 | else if( curr.state === STATE_REJECTED )
|
70 | execute_handlers( curr, 'onRejected', curr.rejectReason );
|
71 | };
|
72 |
|
73 | /* execute particular set of handlers */
|
74 | var execute_handlers = function( curr, name, value ){
|
75 | /* global setImmediate: true */
|
76 | /* global setTimeout: true */
|
77 |
|
78 | /* short-circuit processing */
|
79 | if( curr[ name ].length === 0 )
|
80 | return;
|
81 |
|
82 | /* iterate over all handlers, exactly once */
|
83 | var handlers = curr[ name ];
|
84 | curr[ name ] = []; /* [Promises/A+ 2.2.2.3, 2.2.3.3] */
|
85 | var func = function(){
|
86 | for( var i = 0; i < handlers.length; i++ )
|
87 | handlers[ i ]( value ); /* [Promises/A+ 2.2.5] */
|
88 | };
|
89 |
|
90 | /* execute procedure asynchronously */ /* [Promises/A+ 2.2.4, 3.1] */
|
91 | if( typeof setImmediate === 'function' )
|
92 | setImmediate( func );
|
93 | else
|
94 | setTimeout( func, 0 );
|
95 | };
|
96 |
|
97 | /* generate a resolver function */
|
98 | var resolver = function( cb, next, method ){
|
99 | return function( value ){
|
100 | if( typeof cb !== 'function' ) /* [Promises/A+ 2.2.1, 2.2.7.3, 2.2.7.4] */
|
101 | next[ method ].call( next, value ); /* [Promises/A+ 2.2.7.3, 2.2.7.4] */
|
102 | else {
|
103 | var result;
|
104 | try { result = cb( value ); } /* [Promises/A+ 2.2.2.1, 2.2.3.1, 2.2.5, 3.2] */
|
105 | catch( e ){
|
106 | next.reject( e ); /* [Promises/A+ 2.2.7.2] */
|
107 | return;
|
108 | }
|
109 | resolve( next, result ); /* [Promises/A+ 2.2.7.1] */
|
110 | }
|
111 | };
|
112 | };
|
113 |
|
114 | /* "Promise Resolution Procedure" */ /* [Promises/A+ 2.3] */
|
115 | var resolve = function( promise, x ){
|
116 | /* sanity check arguments */ /* [Promises/A+ 2.3.1] */
|
117 | if( promise === x || promise.proxy === x ){
|
118 | promise.reject( new TypeError( 'cannot resolve promise with itself' ) );
|
119 | return;
|
120 | }
|
121 |
|
122 | /* surgically check for a "then" method
|
123 | (mainly to just call the "getter" of "then" only once) */
|
124 | var then;
|
125 | if( (typeof x === 'object' && x !== null) || typeof x === 'function' ){
|
126 | try { then = x.then; } /* [Promises/A+ 2.3.3.1, 3.5] */
|
127 | catch( e ){
|
128 | promise.reject( e ); /* [Promises/A+ 2.3.3.2] */
|
129 | return;
|
130 | }
|
131 | }
|
132 |
|
133 | /* handle own Thenables [Promises/A+ 2.3.2]
|
134 | and similar "thenables" [Promises/A+ 2.3.3] */
|
135 | if( typeof then === 'function' ){
|
136 | var resolved = false;
|
137 | try {
|
138 | /* call retrieved "then" method */ /* [Promises/A+ 2.3.3.3] */
|
139 | then.call( x,
|
140 | /* resolvePromise */ /* [Promises/A+ 2.3.3.3.1] */
|
141 | function( y ){
|
142 | if( resolved ) return; resolved = true; /* [Promises/A+ 2.3.3.3.3] */
|
143 | if( y === x ) /* [Promises/A+ 3.6] */
|
144 | promise.reject( new TypeError( 'circular thenable chain' ) );
|
145 | else
|
146 | resolve( promise, y );
|
147 | },
|
148 |
|
149 | /* rejectPromise */ /* [Promises/A+ 2.3.3.3.2] */
|
150 | function( r ){
|
151 | if( resolved ) return; resolved = true; /* [Promises/A+ 2.3.3.3.3] */
|
152 | promise.reject( r );
|
153 | }
|
154 | );
|
155 | }
|
156 | catch( e ){
|
157 | if( !resolved ) /* [Promises/A+ 2.3.3.3.3] */
|
158 | promise.reject( e ); /* [Promises/A+ 2.3.3.3.4] */
|
159 | }
|
160 | return;
|
161 | }
|
162 |
|
163 | /* handle other values */
|
164 | promise.fulfill( x ); /* [Promises/A+ 2.3.4, 2.3.3.4] */
|
165 | };
|
166 |
|
167 | // so we always have Promise.all()
|
168 | api.all = function( ps ){
|
169 | return new api(function( resolveAll, rejectAll ){
|
170 | var vals = new Array( ps.length );
|
171 | var doneCount = 0;
|
172 |
|
173 | var fulfill = function( i, val ){
|
174 | vals[ i ] = val;
|
175 | doneCount++;
|
176 |
|
177 | if( doneCount === ps.length ){
|
178 | resolveAll( vals );
|
179 | }
|
180 | };
|
181 |
|
182 | for( var i = 0; i < ps.length; i++ ){
|
183 | (function( i ){
|
184 | var p = ps[i];
|
185 | var isPromise = p != null && p.then != null;
|
186 |
|
187 | if( isPromise ){
|
188 | p.then( function( val ){
|
189 | fulfill( i, val );
|
190 | }, function( err ){
|
191 | rejectAll( err );
|
192 | } );
|
193 | } else {
|
194 | var val = p;
|
195 | fulfill( i, val );
|
196 | }
|
197 | })( i );
|
198 | }
|
199 |
|
200 | } );
|
201 | };
|
202 |
|
203 | api.resolve = function( val ){
|
204 | return new api(function( resolve, reject ){ resolve( val ); });
|
205 | };
|
206 |
|
207 | api.reject = function( val ){
|
208 | return new api(function( resolve, reject ){ reject( val ); });
|
209 | };
|
210 |
|
211 | export default typeof Promise !== 'undefined' ? Promise : api; // eslint-disable-line no-undef
|