├── tests.html
├── promise.min.js
├── tests.js
├── README.md
└── promise.js
/tests.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
7 |
8 |
9 |
20 |
21 |
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/promise.min.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2012-2013 (c) Pierre Duquesne
3 | * Licensed under the New BSD License.
4 | * https://github.com/stackp/promisejs
5 | */
6 | (function(a){function b(){this._callbacks=[];}b.prototype.then=function(a,c){var d;if(this._isdone)d=a.apply(c,this.result);else{d=new b();this._callbacks.push(function(){var b=a.apply(c,arguments);if(b&&typeof b.then==='function')b.then(d.done,d);});}return d;};b.prototype.done=function(){this.result=arguments;this._isdone=true;for(var a=0;a=300)&&j.status!==304);h.done(a,j.responseText,j);}};j.send(k);return h;}function h(a){return function(b,c,d){return g(a,b,c,d);};}var i={Promise:b,join:c,chain:d,ajax:g,get:h('GET'),post:h('POST'),put:h('PUT'),del:h('DELETE'),ENOXHR:1,ETIMEOUT:2,ajaxTimeout:0};if(typeof define==='function'&&define.amd)define(function(){return i;});else a.promise=i;})(this);
--------------------------------------------------------------------------------
/tests.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Useful functions
3 | */
4 |
5 | function success(name){
6 | console.log("Success: ", name);
7 | }
8 | function failure(name){
9 | console.log("Error: ", name);
10 | }
11 |
12 | function assert(bool, name) {
13 | if (bool)
14 | success(name);
15 | else
16 | failure(name);
17 | }
18 |
19 |
20 | function sync_return(value) {
21 | var p = new promise.Promise();
22 | p.done(null, value);
23 | return p;
24 | }
25 |
26 | function async_return(value) {
27 | var p = new promise.Promise();
28 | setTimeout(function(){
29 | p.done(null, value);
30 | });
31 | return p;
32 | }
33 |
34 | function late(n) {
35 | var p = new promise.Promise();
36 | setTimeout(function() {
37 | p.done(null, n);
38 | }, n);
39 | return p;
40 | }
41 |
42 |
43 | /*
44 | * Tests
45 | */
46 |
47 | function test_simple_synchronous() {
48 | sync_return(123).then(function(error, result) {
49 | assert(result === 123, 'simple synchronous test');
50 | });
51 | }
52 |
53 | function test_simple_asynchronous() {
54 | async_return(123).then(function(error, result) {
55 | assert(result === 123, 'simple asynchronous test');
56 | });
57 | }
58 |
59 | function test_multi_results() {
60 | p = new promise.Promise();
61 |
62 | p.then(function (res, a, b, c) {
63 | assert(a === 1, 'multiple results (1/3)');
64 | });
65 |
66 | setTimeout(
67 | function () {
68 | p.then(function (res, a, b, c) {
69 | assert(b === 2, 'multiple results (2/3)');
70 | });
71 |
72 | p.done(null, 1, 2, 3);
73 |
74 | p.then(function (res, a, b, c) {
75 | assert(c === 3, 'multiple results (3/3)');
76 | });
77 | });
78 |
79 | }
80 |
81 | function test_join() {
82 | var d = new Date();
83 |
84 | promise.join([late(400), late(800)]).then(
85 | function(results) {
86 | var delay = new Date() - d;
87 | assert(results[0][1] === 400 && results[1][1] === 800,
88 | "join() result");
89 | assert(700 < delay && delay < 900, "joining functions");
90 | }
91 | );
92 | }
93 |
94 |
95 | function test_join_empty() {
96 | var joined = false;
97 |
98 | promise.join([]).then(
99 | function() {
100 | joined = true;
101 | }
102 | );
103 |
104 | setTimeout(
105 | function() {
106 | assert(joined, "empty join");
107 | }, 200);
108 | }
109 |
110 |
111 | var to_chain = {
112 | d: new Date(),
113 | f1: function() {
114 | return late(100);
115 | },
116 | f2 : function(err, n) {
117 | return late(n + 200);
118 | },
119 | f3: function(err, n) {
120 | return late(n + 300);
121 | },
122 | f4: function(err, n) {
123 | return late(n + 400);
124 | },
125 | check: function(err, n) {
126 | var delay = new Date() - to_chain.d;
127 | assert(n === 1000, "chain() result");
128 | assert(1900 < delay && delay < 2400, "chaining functions()");
129 | }
130 | };
131 |
132 | function test_then_then() {
133 | var p = new promise.Promise();
134 | p.then(
135 | to_chain.f1
136 | ).then(
137 | to_chain.f2
138 | ).then(
139 | to_chain.f3
140 | ).then(
141 | to_chain.f4
142 | ).then(
143 | to_chain.check
144 | );
145 | }
146 |
147 | function test_chain() {
148 | promise.chain(
149 | [to_chain.f1,
150 | to_chain.f2,
151 | to_chain.f3,
152 | to_chain.f4]
153 | ).then(
154 | to_chain.check
155 | );
156 | }
157 |
158 | function test_ajax_timeout () {
159 | var realXMLHttpRequest = window.XMLHttpRequest;
160 | var isAborted = false;
161 | var defaultTimeout = promise.ajaxTimeout;
162 | promise.ajaxTimeout = 2000;
163 |
164 | window.XMLHttpRequest = function () {
165 | this.readyState = 4;
166 | this.status = 200;
167 | this.responseText = 'a response text';
168 | this.open = function () {};
169 | this.setRequestHeader = function () {};
170 | this.abort = function () { isAborted = true; };
171 | this.onreadystatechange = function () {};
172 | var self = this;
173 | this.send = function () {
174 | setTimeout(function() {
175 | self.onreadystatechange();
176 | }, 3000);
177 | };
178 | };
179 |
180 | promise.get('/').then(
181 | function(err, text, xhr) {
182 | assert(isAborted === true, 'Ajax timeout must abort xhr');
183 | assert(err === promise.ETIMEOUT, 'Ajax timeout must report error');
184 | assert(text === '', 'Ajax timeout must return empty response');
185 |
186 | window.XMLHttpRequest = realXMLHttpRequest;
187 | promise.ajaxTimeout = defaultTimeout;
188 | });
189 | }
190 |
191 |
192 | function test() {
193 | test_simple_synchronous();
194 | test_simple_asynchronous();
195 | test_multi_results();
196 | test_join();
197 | test_join_empty();
198 | test_then_then();
199 | test_chain();
200 | test_ajax_timeout();
201 | }
202 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # promise.js
2 |
3 | A lightweight javascript implementation of promises.
4 |
5 | ## Using the `Promise` Object
6 |
7 | Promises provide an alternative to callback-passing. Asynchronous functions return a `Promise` object onto which callbacks can be attached.
8 |
9 | Callbacks are attached using the `.then(callback)` method. They will be called when the promise is resolved.
10 |
11 | ```js
12 | var p = asyncfoo(a, b, c);
13 |
14 | p.then(function(error, result) {
15 | if (error) return;
16 | alert(result);
17 | });
18 | ```
19 |
20 | Asynchronous functions must resolve the promise with the `.done()` method when their task is done. This invokes the promise callback(s) with the same arguments that were passed to `.done()`.
21 |
22 | ```js
23 | function asyncfoo() {
24 |
25 | var p = new promise.Promise(); /* (1) create a Promise */
26 |
27 | setTimeout(function() {
28 | p.done(null, "O hai!"); /* (3) resolve it when ready */
29 | }, 1000);
30 |
31 | return p; /* (2) return it */
32 | }
33 | ```
34 |
35 | ## A Word on Callback Signatures
36 |
37 | Although an arbitrary number of arguments are accepted for callbacks, the following signature is recommended: `callback(error, result)`.
38 |
39 | The `error` parameter can be used to pass an error code such that `error != false` in case something went wrong; the `result` parameter is used to pass a value produced by the asynchronous task. This allows callbacks to be written like this:
40 |
41 | ```js
42 | function callback(error, result) {
43 | if (error) {
44 | /* Deal with error case. */
45 | ...
46 | return;
47 | }
48 |
49 | /* Deal with normal case. */
50 | ...
51 | }
52 | ```
53 |
54 | ## Chaining Asynchronous Functions
55 |
56 | There are two ways of chaining asynchronous function calls. The first one is to make the callback return a promise object and to chain `.then()` calls. Indeed, `.then()` returns a `Promise` that is resolved when the callback resolves its promise.
57 |
58 | **Example:**
59 |
60 | ```js
61 | function late(n) {
62 | var p = new promise.Promise();
63 | setTimeout(function() {
64 | p.done(null, n);
65 | }, n);
66 | return p;
67 | }
68 |
69 | late(100).then(
70 | function(err, n) {
71 | return late(n + 200);
72 | }
73 | ).then(
74 | function(err, n) {
75 | return late(n + 300);
76 | }
77 | ).then(
78 | function(err, n) {
79 | return late(n + 400);
80 | }
81 | ).then(
82 | function(err, n) {
83 | alert(n);
84 | }
85 | );
86 | ```
87 |
88 | The other option is to use `promise.chain()`. The function expects an array of asynchronous functions that return a promise each. `promise.chain()` itself returns a `Promise`.
89 |
90 | ```js
91 | promise.chain([f1, f2, f3, ...]);
92 | ```
93 |
94 | **Example:**
95 |
96 | ```js
97 | function late(n) {
98 | var p = new promise.Promise();
99 | setTimeout(function() {
100 | p.done(null, n);
101 | }, n);
102 | return p;
103 | }
104 |
105 | promise.chain([
106 | function() {
107 | return late(100);
108 | },
109 | function(err, n) {
110 | return late(n + 200);
111 | },
112 | function(err, n) {
113 | return late(n + 300);
114 | },
115 | function(err, n) {
116 | return late(n + 400);
117 | }
118 | ]).then(
119 | function(err, n) {
120 | alert(n);
121 | }
122 | );
123 | ```
124 |
125 | ## Joining Functions
126 |
127 | promise.join([p1, p2, p3, ...]);
128 |
129 | `promise.join()` expects an array of `Promise` object and returns a `Promise` that will be resolved once all the arguments have been resolved. The callback will be passed an array containing the values passed by each promise, in the same order that the promises were given.
130 |
131 | **Example**:
132 |
133 | ```js
134 | function late(n) {
135 | var p = new promise.Promise();
136 | setTimeout(function() {
137 | p.done(null, n);
138 | }, n);
139 | return p;
140 | }
141 |
142 | promise.join([
143 | late(400),
144 | late(800)
145 | ]).then(
146 | function(results) {
147 | var res0 = results[0];
148 | var res1 = results[1];
149 | alert(res0[1] + " " + res1[1]);
150 | }
151 | );
152 | ```
153 |
154 | ## AJAX Functions Included
155 |
156 | Because AJAX requests are the root of much asynchrony in Javascript, promise.js provides the following functions:
157 |
158 | ```js
159 | promise.get(url, data, headers)
160 | promise.post(url, data, headers)
161 | promise.put(url, data, headers)
162 | promise.del(url, data, headers)
163 | ```
164 |
165 | `data` *(optional)* : a {key: value} object or url-encoded string.
166 |
167 | `headers` *(optional)* : a {key: value} object (e.g. `{"Accept": "application/json"}`).
168 |
169 | **Example**:
170 |
171 | ```js
172 | promise.get('/').then(function(error, text, xhr) {
173 | if (error) {
174 | alert('Error ' + xhr.status);
175 | return;
176 | }
177 | alert('The page contains ' + text.length + ' character(s).');
178 | });
179 | ```
180 |
181 | You can set a time in milliseconds after which unresponsive AJAX
182 | requests should be aborted. This is a global configuration option,
183 | disabled by default.
184 |
185 | /* Global configuration option */
186 | promise.ajaxTimeout = 10000;
187 |
188 |
189 | ## Browser compatibility
190 |
191 | The library has been successfully tested on IE5.5+ and FF1.5+
192 |
193 | ## License
194 |
195 | promise.js is licensed under the New BSD License.
196 |
197 |
198 | Have fun!
199 |
--------------------------------------------------------------------------------
/promise.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2012-2013 (c) Pierre Duquesne
3 | * Licensed under the New BSD License.
4 | * https://github.com/stackp/promisejs
5 | */
6 |
7 | (function(exports) {
8 |
9 | function Promise() {
10 | this._callbacks = [];
11 | }
12 |
13 | Promise.prototype.then = function(func, context) {
14 | var p;
15 | if (this._isdone) {
16 | p = func.apply(context, this.result);
17 | } else {
18 | p = new Promise();
19 | this._callbacks.push(function () {
20 | var res = func.apply(context, arguments);
21 | if (res && typeof res.then === 'function')
22 | res.then(p.done, p);
23 | });
24 | }
25 | return p;
26 | };
27 |
28 | Promise.prototype.done = function() {
29 | this.result = arguments;
30 | this._isdone = true;
31 | for (var i = 0; i < this._callbacks.length; i++) {
32 | this._callbacks[i].apply(null, arguments);
33 | }
34 | this._callbacks = [];
35 | };
36 |
37 | function join(promises) {
38 | var p = new Promise();
39 | var results = [];
40 |
41 | if (!promises || !promises.length) {
42 | p.done(results);
43 | return p;
44 | }
45 |
46 | var numdone = 0;
47 | var total = promises.length;
48 |
49 | function notifier(i) {
50 | return function() {
51 | numdone += 1;
52 | results[i] = Array.prototype.slice.call(arguments);
53 | if (numdone === total) {
54 | p.done(results);
55 | }
56 | };
57 | }
58 |
59 | for (var i = 0; i < total; i++) {
60 | promises[i].then(notifier(i));
61 | }
62 |
63 | return p;
64 | }
65 |
66 | function chain(funcs, args) {
67 | var p = new Promise();
68 | if (funcs.length === 0) {
69 | p.done.apply(p, args);
70 | } else {
71 | funcs[0].apply(null, args).then(function() {
72 | funcs.splice(0, 1);
73 | chain(funcs, arguments).then(function() {
74 | p.done.apply(p, arguments);
75 | });
76 | });
77 | }
78 | return p;
79 | }
80 |
81 | /*
82 | * AJAX requests
83 | */
84 |
85 | function _encode(data) {
86 | var payload = "";
87 | if (typeof data === "string") {
88 | payload = data;
89 | } else {
90 | var e = encodeURIComponent;
91 | var params = [];
92 |
93 | for (var k in data) {
94 | if (data.hasOwnProperty(k)) {
95 | params.push(e(k) + '=' + e(data[k]));
96 | }
97 | }
98 | payload = params.join('&')
99 | }
100 | return payload;
101 | }
102 |
103 | function new_xhr() {
104 | var xhr;
105 | if (window.XMLHttpRequest) {
106 | xhr = new XMLHttpRequest();
107 | } else if (window.ActiveXObject) {
108 | try {
109 | xhr = new ActiveXObject("Msxml2.XMLHTTP");
110 | } catch (e) {
111 | xhr = new ActiveXObject("Microsoft.XMLHTTP");
112 | }
113 | }
114 | return xhr;
115 | }
116 |
117 |
118 | function ajax(method, url, data, headers) {
119 | var p = new Promise();
120 | var xhr, payload;
121 | data = data || {};
122 | headers = headers || {};
123 |
124 | try {
125 | xhr = new_xhr();
126 | } catch (e) {
127 | p.done(promise.ENOXHR, "");
128 | return p;
129 | }
130 |
131 | payload = _encode(data);
132 | if (method === 'GET' && payload) {
133 | url += '?' + payload;
134 | payload = null;
135 | }
136 |
137 | xhr.open(method, url);
138 |
139 | var content_type = 'application/x-www-form-urlencoded';
140 | for (var h in headers) {
141 | if (headers.hasOwnProperty(h)) {
142 | if (h.toLowerCase() === 'content-type')
143 | content_type = headers[h];
144 | else
145 | xhr.setRequestHeader(h, headers[h]);
146 | }
147 | }
148 | xhr.setRequestHeader('Content-type', content_type);
149 |
150 |
151 | function onTimeout() {
152 | xhr.abort();
153 | p.done(promise.ETIMEOUT, "", xhr);
154 | }
155 |
156 | var timeout = promise.ajaxTimeout;
157 | if (timeout) {
158 | var tid = setTimeout(onTimeout, timeout);
159 | }
160 |
161 | xhr.onreadystatechange = function() {
162 | if (timeout) {
163 | clearTimeout(tid);
164 | }
165 | if (xhr.readyState === 4) {
166 | var err = (!xhr.status ||
167 | (xhr.status < 200 || xhr.status >= 300) &&
168 | xhr.status !== 304);
169 | p.done(err, xhr.responseText, xhr);
170 | }
171 | };
172 |
173 | xhr.send(payload);
174 | return p;
175 | }
176 |
177 | function _ajaxer(method) {
178 | return function(url, data, headers) {
179 | return ajax(method, url, data, headers);
180 | };
181 | }
182 |
183 | var promise = {
184 | Promise: Promise,
185 | join: join,
186 | chain: chain,
187 | ajax: ajax,
188 | get: _ajaxer('GET'),
189 | post: _ajaxer('POST'),
190 | put: _ajaxer('PUT'),
191 | del: _ajaxer('DELETE'),
192 |
193 | /* Error codes */
194 | ENOXHR: 1,
195 | ETIMEOUT: 2,
196 |
197 | /**
198 | * Configuration parameter: time in milliseconds after which a
199 | * pending AJAX request is considered unresponsive and is
200 | * aborted. Useful to deal with bad connectivity (e.g. on a
201 | * mobile network). A 0 value disables AJAX timeouts.
202 | *
203 | * Aborted requests resolve the promise with a ETIMEOUT error
204 | * code.
205 | */
206 | ajaxTimeout: 0
207 | };
208 |
209 | if (typeof define === 'function' && define.amd) {
210 | /* AMD support */
211 | define(function() {
212 | return promise;
213 | });
214 | } else {
215 | exports.promise = promise;
216 | }
217 |
218 | })(this);
219 |
--------------------------------------------------------------------------------