Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Newer
Older
100644 422 lines (360 sloc) 9.455 kb
2120e90e »
2011-06-21 .
1 /***
2
3 Promise JS
4
5
6
7 **/
8
9 exports.promisePrototype = {};
10 exports.continuationPrototype = {};
11 exports.errorPrototype = {};
12
13
14 /*
15 Creates a wrapper function
16 to avoid anny mutation on incoming functions
17 */
18 var wrapFn = function(f) {
19 return function() {
20 return f.apply(this, arguments);
21 };
22 };
23
24 /*
25 takes a map witch it extends with keys from the second
26 map
27 */
28 var extend = function(m1, m2) {
29 for(key in m2) {
30 m1[key] = m2[key];
31 }
32 };
33
34 /**
35 se extend:
36 assoc wont mutate the original object
37 */
38 var assoc = function(m1, m2) {
39 var res = {};
40 extend(res, m1);
41 extend(res, m2);
42 return res;
43 };
44
45 /*
46 the promise function creates a promise
47 by taking a function that resives a continuation
48 witch it will call with the data that whas promised
49
50 note the continuation is not the subscribing function it
51 has methods and deals with errors and context.
52
53
54 */
55 exports.promise = function(f) {
56 var promise = function(contParent, f) {
57
58 var parent = undefined; // argument shuffeling for nicer api
59 if(f == undefined) {
60 var f = contParent; }
61 else { parent = contParent;}
62
63 var called = false; // state for error handeling
64 var cont = function(data) {
65 if(called) {
66 return cont.throwError("dead-continuation-call.core", {});
67 } else {
68 called = true;
69 try { return f(data); }
70 catch(e) {
71 cont.throwError("try-catch.core", {
72 error: e
73 });
74 }
75 }
76 };
77 cont.__proto__ = exports.continuationPrototype;
78 if(parent != undefined) {
79 cont.parent = parent;
80 }
81 cont.promise = promise;
82 try { return promise.fn(cont); } // the call of the provided function
83 catch(e) {
84 cont.throwError("try-catch.core", { error: e });
85 }
86 };
87 promise.__proto__ = exports.promisePrototype;
88 promise.fn = f;
89 return promise;
90 };
91
92 var promise = exports.promise;
93
94 extend(exports, {
95
96 terminateProssesWith: function(error) {
97 console.log("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!");
98 console.log("! Application Terminated becuse of uncatched error !");
99 console.log("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n");
100 console.log("Uncatched Error: " + error.name);
101 console.dir(error);
102 console.log();
103 process.exit();
104 },
105
106 /* wrapps a value in a promise */
107 result: function(data) {
108 return promise(function(cont) {
109 cont(data);
110 });
111 },
112
113 /* gives you a promise witch will throw an error */
114 error: function(name, obj) {
115 return this.throwable(name, obj);
116 },
117
118 throwable: function(namespace, obj) {
119 return promise(function(cont) {
120 cont.throwError(namespace, obj);
121 });
122 },
123
124 /*
125 takes a map of promise values
126 and gives you a promis of a map
127 with the values of the promises in the map
128
129 esentialy wait for all
130 */
131 syncMap: function(map) {
132 return promise(function(cont) {
133 var resultMap = {};
134 var mapItemCount = Object.keys(map).length;
135 var completedCount = 0;
136 for(key in map) {
137 (function() {
138 var k = key;
139 map[k](cont, function(data) {
140 resultMap[k] = data;
141 completedCount++;
142 if(completedCount == mapItemCount) {
143 cont(resultMap);
144 }
145 });
146 })();
147 }
148 });
149 },
150
151 /* takes a function and some arguments and returns you a function
152 witch will use your argument as the first args */
153 cur: function(f) {
154 var args = arguments.length > 1 ? arguments.slice(1) : [];
155 return function() {
156 return f.apply(this, args.concat(arguments));
157 };
158 },
159
160 sync: function(coll, args) {
161 if(coll.constructor === Array) {
162 return exports.syncArray(coll, args);
163 } else {
164 return exports.syncMap(coll, args);
165 }
166 },
167
168 /* TODO: write tests forthis */
169
170 syncArrayScramble: function(coll) {
171 return promise(function(cont) {
172 resultArray = [];
173 for(var i = 0; i < coll.length; i++) {
174 coll[i] (cont, function(data) {
175 resultArray.push(data);
176 if(resultArray.length == coll.length) {
177 cont(resultArray);
178 }
179 });
180 }
181 });
182 },
183
184 /*
185 !! EXPERMANTAL
186
187 a stream a sequens of values
188 pushed and handeled by the
189 promise
190
191 it takes a function witch takes a stream continuation
192 with the extra methods next and complete
193
194 */
195 stream: function(f) {
196 return promise(function(cont) {
197 cont([]);
198 });
199 },
200
201
202 /**/
203 readChunked: function(args) {
204 var chunkSize = args.chunksSize || 4;
205
206 var chunksCount =
207 1 + parseInt(("" + args.coll.length / chunkSize).split(".") [0]);
208
209
210
211 },
212
213 /*
214 same as sync map but takes an array of promises
215 and returns a promise that will wait for all of them
216 and give you an array of values (order is maintained)
217 */
218 syncArray: function(coll, args) {
219 args = args || {};
220 if(args.ordered != undefined && args.ordered == false) {
221 return exports.syncArrayScramble(coll);
222 }
223
224 return promise(function(cont) {
225 var resultArray = new Array(coll.length);
226 var completedCount = 0;
227 for(var i = 0; i < coll.length; i++) {
228 (function() {
229 var z = i;
230 coll[z](cont, function(data) {
231 resultArray[z] = data;
232 completedCount++;
233 if(completedCount == coll.length) {
234 cont(resultArray);
235 }
236 });
237 })();
238 }
239 });
240 }
241 });
242
243 extend(exports.promisePrototype, {
244
245 context: function(name, val) {
246 var self = this;
247 return promise(function(cont) {
248 return self(cont.withContext(name, val), function(data) {
249 return cont(data);
250 });
251 });
252 },
253
254 interact: function(f) {
255 return this.bind(function(data) {
256 return promise(function(cont) {
257 return f(data, cont);
258 });
259 });
260 },
261
262 map: function(f) {
263 var self = this;
264 return this.bind(function(data) {
265 try { return exports.result(f(data)); }
266 catch(e) {
267 return exports.throwable("try-catch.core", { error: e });
268 }
269 });
270 },
271
272 bind: function(f) {
273 var self = this;
274 return promise(function(cont) {
275 return self(cont, function(data) {
276 return f(data)(cont, function(data) {
277 cont(data);
278 });
279 });
280 });
281 },
282
283 joinWith: function(p2, f) {
284 var p1 = this;
285 return promise(function(cont) {
286 var p1d = undefined;
287 var p2d = undefined;
288
289 var go = function() {
290 if(p1d != undefined && p2d != undefined) {
291 try { cont(f(p1d, p2d)); }
292 catch(e) {
293 cont.throwError("try-catch.core", {
294 error: e
295 });
296 }
297 }
298 };
299
300 p1(cont, function(data) {
301 p1d = data;
302 go();
303 });
304
305 p2(cont, function(data) {
306 p2d = data;
307 go();
308 });
309 });
310 },
311
312 maps: function() {
313 var curent = this;
314 for(i in arguments) {
315 curent = curent.map(arguments[i]);
316 }
317 return curent;
318 },
319
320 timeout: function(time) {
321 var self = this;
322 return promise(function(cont) {
323 var timedOut = false;
324 var completed = false;
325 setTimeout(function() {
326 if(!completed) {
327 timedOut = true;
328 cont.throwError("timeout.core", {});
329 }
330 }, time);
331
332 self(cont, function(data) {
333 if(!timedOut) {
334 cont(data);
335 }
336 });
337 });
338 },
339
340 catching: function(namespace, catcher) {
341 var self = this;
342 return promise(function(cont) {
343 return self(cont.withCatcher(namespace, catcher), function(data) {
344 return cont(data);
345 });
346 });
347 }
348 });
349
350
351
352 extend(exports.continuationPrototype, {
353
354 withCatcher: function(namespace, catcher) {
355 var catchers = this.catchers || {};
356 var cont = wrapFn(this);
357 cont.__proto__ = exports.continuationPrototype;
358 cont.parent = this;
359 var c = {};
360 c[namespace] = catcher;
361 cont.catchers = assoc(catchers, c);
362 return cont;
363 },
364
365 withContext: function(name, val) {
366 var contextMap = this.contextMap || {};
367 var cont = wrapFn(this);
368 cont.__proto__ = exports.continuationPrototype;
369 cont.parent = this;
370 var c = {};
371 c[name] = val;
372 cont.contextMap = assoc(contextMap, c);
373 return cont;
374 },
375
376 context: function(name) {
377 if(this.contextMap != undefined && this.contextMap[name] != undefined) {
378 return this.contextMap[name];
379 } else {
380 if(this.parent != undefined) {
381 return this.parent.context(name);
382 } else {
383 return undefined;
384 }
385 }
386 },
387
388 /**
389 a1 -> catcher -> a2
390 */
391 throwError: function(namespace, opts) {
392 var error = {};
393 extend(error, opts);
394 error.__proto__ = exports.errorPrototype;
395 error.name = namespace;
396 error.continuation = this;
397
398 var searchThis = function(e) {
399 var catchers = e.catchers || {};
400 if(catchers[namespace] != undefined) {
401 catchers[namespace](error);
402 } else {
403 if(e.parent != undefined) {
404 searchThis(e.parent);
405 } else {
406 exports.terminateProssesWith(error);
407 }
408 }
409 };
410 searchThis(this);
411 },
412
413 timeout: function(info) {
414 return this.throwError("timeout.core", info);
415 }
416 });
417
418 extend(exports.errorPrototype, {
419 continueWith: function(value) {
420 this.continuation(value);
421 }
422 });
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
Something went wrong with that request. Please try again.