UNPKG

7.59 kBJavaScriptView Raw
1/*!
2Embeddable Minimum Strictly-Compliant Promises/A+ 1.1.1 Thenable
3Copyright (c) 2013-2014 Ralf S. Engelschall (http://engelschall.com)
4Licensed under The MIT License (http://opensource.org/licenses/MIT)
5*/
6
7/* promise states [Promises/A+ 2.1] */
8var STATE_PENDING = 0; /* [Promises/A+ 2.1.1] */
9var STATE_FULFILLED = 1; /* [Promises/A+ 2.1.2] */
10var STATE_REJECTED = 2; /* [Promises/A+ 2.1.3] */
11
12/* promise object constructor */
13var 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 */
37api.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 */
56var 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 */
66var 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 */
74var 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 */
98var 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] */
115var 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()
168api.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
203api.resolve = function( val ){
204 return new api(function( resolve, reject ){ resolve( val ); });
205};
206
207api.reject = function( val ){
208 return new api(function( resolve, reject ){ reject( val ); });
209};
210
211export default typeof Promise !== 'undefined' ? Promise : api; // eslint-disable-line no-undef