├── .gitignore ├── package.json ├── ws-jsgi.js ├── jsgi └── node.js ├── README.md ├── jsgi-node.js └── promise.js /.gitignore: -------------------------------------------------------------------------------- 1 | *~ 2 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "jsgi-node", 3 | "version": "0.3.3", 4 | "directories": { "lib": "." }, 5 | "main": "./jsgi-node", 6 | "description": "JSGI middleware server for NodeJS", 7 | "author": "Kris Zyp", 8 | "maintainers": [ 9 | { 10 | "name": "Kris Zyp", 11 | "email": "kriszyp@gmail.com" 12 | } 13 | ], 14 | "repository": { 15 | "type": "git", 16 | "url": "https://github.com/kriszyp/jsgi-node.git" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /ws-jsgi.js: -------------------------------------------------------------------------------- 1 | var when = require("./promise").when, 2 | NodeRequest = require("./jsgi-node").Request; 3 | 4 | 5 | module.exports = function(socketServer, jsgiApp){ 6 | socketServer.on("connection", function(socket){ 7 | var req = socket.upgradeReq; 8 | req.setTimeout(0); 9 | function Request(){} 10 | Request.prototype = new NodeRequest(req); 11 | function Headers(){} 12 | Headers.prototype = Request.prototype.headers; 13 | socket.on("message", function(data){ 14 | var request = new Request(); 15 | request.body = [data]; 16 | request.headers = new Headers(); 17 | when(jsgiApp(request), function(response){ 18 | when(response.body, function(body){ 19 | var chunks = [], 20 | done = false; 21 | when(body.forEach(function (chunk) { 22 | chunks.push(chunk); 23 | }), function () { 24 | done = true; 25 | }); 26 | socket.stream(function (err, send) { 27 | if (!err && chunks.length) { 28 | send(chunks.join(''), done); 29 | chunks = []; 30 | } 31 | }); 32 | }); 33 | }); 34 | }); 35 | }); 36 | }; 37 | -------------------------------------------------------------------------------- /jsgi/node.js: -------------------------------------------------------------------------------- 1 | var when = require("../promise").when, 2 | defer = require("../promise").defer; 3 | // takes a Node HTTP app and runs it on top of a JSGI stack 4 | exports.Node = function(nodeApp){ 5 | return function(request){ 6 | var endListeners = []; 7 | var bodyDeferred; 8 | var responseDeferred = defer(); 9 | var nodeRequest = { 10 | headers: request.headers, 11 | httpVersionMajor: request.version[0], 12 | httpVersionMinor: request.version[1], 13 | addListener: function(event, callback){ 14 | process.nextTick(function(){ 15 | if(event == "data"){ 16 | when(request.body && request.body.forEach(function(data){ 17 | callback(data); 18 | }), function(){ 19 | endListeners.forEach(function(listener){ 20 | listener(); 21 | }); 22 | endListeners = null; 23 | }); 24 | } 25 | if(event == "end"){ 26 | if(endListeners){ 27 | endListeners.push(callback); 28 | }else{ 29 | callback(); 30 | } 31 | } 32 | }); 33 | return this; 34 | }, 35 | pause: function(){ 36 | 37 | }, 38 | resume: function(){ 39 | 40 | } 41 | } 42 | nodeRequest.on = nodeRequest.addListener; 43 | nodeApp(nodeRequest, 44 | { 45 | writeHead: function(status, headers){ 46 | var write; 47 | bodyDeferred = defer(); 48 | responseDeferred.resolve({ 49 | status: status, 50 | headers: headers, 51 | body: { 52 | forEach: function(callback){ 53 | write = callback; 54 | return bodyDeferred.promise; 55 | } 56 | } 57 | }); 58 | }, 59 | write: function(data){ 60 | write(data); 61 | }, 62 | end: function(data){ 63 | write(data); 64 | bodyDeferred.resolve(); 65 | } 66 | }); 67 | return responseDeferred.promise; 68 | } 69 | }; -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # JSGI 0.3 Adapter for Node 2 | 3 | JSGI-Node provides an interface for running [JSGI](http://wiki.commonjs.org/wiki/JSGI/Level0/A/Draft2) middleware on Node. 4 | JSGI is an asynchronous middleware interface based on solid mature middleware design 5 | principles, and the asynchronous design fits perfectly with Node. JSGI uses idiomatic JavaScript, 6 | leveraging closures for [simple and fast](http://www.sitepen.com/blog/2010/06/11/jsgi-vs-connect-for-node-middleware/) middleware connectivity. 7 | This project does not include any JSGI components itself, but 8 | a substantial set of JSGI middleware components that are available, many can be found 9 | in [Pintura](https://github.com/persvr/pintura). 10 | 11 | # Installation 12 | 13 | JSGI-Node can be installed with NPM: 14 | 15 | npm install jsgi-node 16 | 17 | # Usage 18 | 19 | To use, provide a JSGI application (can be application stack) to the start 20 | function: 21 | 22 | require("jsgi-node").start(function(request){ 23 | return { 24 | status:200, 25 | headers:{}, 26 | body:["Hello World!"] 27 | }; 28 | }); 29 | 30 | This adapter should conform to the JSGI 0.3 (with promises) for full 31 | asynchronous support. For example, here is an echo server that asynchronously 32 | waits for the request and asynchonously provides it as the response: 33 | 34 | 35 | require("jsgi-node").start(function(request){ 36 | return request.body.join().then(function(requestBody){ 37 | return { 38 | status:200, 39 | headers:{}, 40 | body:["echo: " + requestBody] 41 | }; 42 | }); 43 | }); 44 | 45 | And here is an example of using a promises from another source (from [promised-io's fs](http://github.com/persvr/promised-io)) and piping them to the 46 | response: 47 | 48 | var fs = require("promised-io/fs"); 49 | require("jsgi-node").start(function(request){ 50 | return fs.readFile("jsgi-node.js").then(function(body){ 51 | return { 52 | status: 200, 53 | headers: {}, 54 | body: [body] 55 | }; 56 | }); 57 | }); 58 | 59 | 60 | File objects returned from [promised-io's fs](http://github.com/persvr/promised-io) can be directly provided as body for 61 | automated streaming of data to the client from the filesystem: 62 | 63 | var fs = require("promised-io/fs"); 64 | require("jsgi-node").start(function(request){ 65 | return { 66 | status: 200, 67 | headers: {}, 68 | body: fs.open("some-file.txt","r") 69 | }; 70 | }); 71 | 72 | This package also includes an adapter for running Node HTTP apps on top of JSGI middleware: 73 | 74 | var fs = require("promised-io/fs"), 75 | Node = require("jsgi/node").Node; 76 | require("jsgi-node").start( 77 | SomeJSGIMiddleWare( 78 | OtherJSGIMiddleWare( 79 | Node(function(request, response){ 80 | // request and response conform to Node's HTTP API 81 | }) 82 | ) 83 | ) 84 | ); 85 | 86 | ## WebSocket with JSGI 87 | 88 | JSGI middleware can be used to handle incoming WebSocket messages. While JSGI 89 | is designed for HTTP, WebSocket includes HTTP elements and JSGI's streaming capabilities 90 | are well-suited for socket communication. JSGI delegation can be achieved by using 91 | the "ws-jsgi" module in conjunction with the node-websocket-server package. 92 | This "ws-jsgi" module exports a function that can be called with a socket server and 93 | a JSGI handler. For example: 94 |
95 | var http = require("http").createServer(
96 | require("jsgi-node").Listener(jsgiApp)
97 | );
98 | http.listen(80);
99 | require("jsgi/ws-jsgi")(ws.createServer({
100 | server: http
101 | }), jsgiApp);
102 |
103 |
104 | Licensing
105 | --------
106 |
107 | The JSGI-Node package is an implementation of JSGI. JSGI is a standard that was
108 | developed in collaboration by many developers through the forums of JackJS,
109 | CommonJS, and Persevere. The implementation in this package is part of the Persevere
110 | project, and therefore is licensed under the
111 | AFL or BSD license. The Persevere project is administered under the Dojo foundation,
112 | and all contributions require a Dojo CLA.
113 |
114 | Authors include Kris Zyp and Jed Schmidt.
--------------------------------------------------------------------------------
/jsgi-node.js:
--------------------------------------------------------------------------------
1 | /*
2 | JSGI 0.3 Adapter for Node
3 |
4 | To use provide a JSGI application (can be application stack) to the start
5 | function:
6 |
7 | require("jsgi-node").start(function(request){
8 | return request.body.join().then(function(requestBody){
9 | return {
10 | status:200,
11 | headers:{},
12 | body:["echo: " + requestBody]
13 | };
14 | });
15 | });
16 |
17 | This adapter should conform to the JSGI 0.3 (with promises) for full
18 | asynchronous support. For example:
19 |
20 | var fs = require("promised-io/fs");
21 | require("jsgi-node").start(function(request){
22 | return fs.readFile("jsgi-node.js").then(function(body){
23 | return {
24 | status: 200,
25 | headers: {},
26 | body: [body]
27 | };
28 | });
29 | });
30 | */
31 |
32 | var
33 | sys = require( "sys" ),
34 | url = require( "url" ),
35 | defer = require("./promise").defer;
36 |
37 | function Request( request ) {
38 | var url = request.url;
39 | var questionIndex = url.indexOf("?");
40 | this.method = request.method;
41 | this.nodeRequest = request;
42 | this.headers = request.headers;
43 | if(questionIndex > -1){
44 | this.pathInfo = url.substring(0, questionIndex);
45 | this.queryString = url.substring(questionIndex + 1);
46 | }
47 | else{
48 | this.pathInfo = url;
49 | this.queryString = "";
50 | }
51 | if(this.method != "GET"){ // optimize GET
52 | this.body = new Input( request );
53 | }
54 |
55 | }
56 |
57 | Request.prototype = {
58 | jsgi:{
59 | version: [ 0, 3 ],
60 | multithread: false,
61 | multiprocess: true,
62 | async: true,
63 | runOnce: false,
64 | errors: {
65 | print: sys.puts,
66 | flush: function(){}
67 | }
68 | },
69 | get env(){
70 | return this._env || (this._env = {});
71 | },
72 | scriptName: "",
73 | scheme:"http",
74 | get host(){
75 | var host = this.headers.host;
76 | return host ? host.split(":")[0] : "";
77 | },
78 | get port(){
79 | var host = this.headers.host;
80 | return host ? (host.split(":")[1] || 80) : 80;
81 | },
82 | get remoteAddr(){
83 | return this.nodeRequest.connection.remoteAddress;
84 | },
85 | get version(){
86 | return [ this.nodeRequest.httpVersionMajor, this.nodeRequest.httpVersionMinor ]
87 | }
88 | };
89 |
90 |
91 | function Input( request ) {
92 | var
93 | inputBuffer = [],
94 | waitingForLength = Infinity;
95 | function callback(data){
96 | inputBuffer.push(data);
97 | }
98 | var deferred = defer();
99 | request
100 | .addListener( "data", function( data ) {
101 | callback(data);
102 | })
103 | .addListener( "end", function() {
104 | deferred.resolve();
105 | });
106 |
107 | this.forEach = function (each) {
108 | if (this.encoding) {
109 | request.setBodyEncoding( this.encoding );
110 | }
111 | inputBuffer.forEach(each);
112 | callback = each;
113 | return deferred.promise;
114 | };
115 | }
116 |
117 | Input.prototype.join = function(token){
118 | var parts = [];
119 | return this.forEach(function(part){
120 | parts.push(part);
121 | }).then(function(){
122 | return parts.join(token || ""); // yeah, I know Array.prototype.join defaults to ",", but clearly "" is more useful here
123 | });
124 | }
125 |
126 | function Response( response, stream ) {
127 | var started = false, canceller, cancel;
128 | return handle;
129 |
130 | function handle( data, notDone ) {
131 | var forEachResult;
132 | if ( typeof data.then === "function" ) {
133 | if(!canceller){
134 | stream.removeAllListeners("close");
135 | canceller = function(){
136 | stream.removeListener("close", canceller);
137 | cancel && cancel();
138 | }
139 | stream.addListener("close", canceller);
140 | }
141 | cancel = data.cancel;
142 | data.then(
143 | handle,
144 | function( error ) {
145 | sys.puts("Error: " + error.stack);
146 | handle({ status:500, headers:{}, body:[error.message] });
147 | },
148 | function( data ){
149 | handle( data, true);
150 | }
151 | );
152 |
153 | return;
154 | }
155 | if ( !started ) {
156 | started = true;
157 | response.writeHead( data.status || 500, data.headers );
158 | }
159 |
160 | try {
161 | if ( typeof data.body === "string" ) {
162 | response.write(data.body);
163 | }
164 | else if ( typeof data.body.forEach !== "function" ) {
165 | throw new Error("The body does not have a forEach function");
166 | }
167 | else {
168 | forEachResult = data.body.forEach( function( chunk ) {
169 | try{
170 | response.write( chunk, data.body.encoding || "utf8" );
171 | }catch(e){
172 | sys.puts( "error writing " + chunk + e);
173 | }
174 | });
175 | }
176 |
177 | if ( !notDone && forEachResult && ( typeof forEachResult.then === "function" ) ) {
178 | cancel = forEachResult.cancel;
179 | forEachResult.then( function() {
180 | if(canceller){
181 | stream.addListener("close", canceller);
182 | }
183 | response.end();
184 | });
185 | }
186 |
187 | else if ( !notDone ) {
188 | if(canceller){
189 | stream.addListener("close", canceller);
190 | }
191 | response.end();
192 | }
193 | }
194 |
195 | catch( e ) {
196 | if(canceller){
197 | stream.addListener("close", canceller);
198 | }
199 | try{
200 | // if it is not too late, set the status
201 | if(!response.statusCode){
202 | response.writeHead(500, {});
203 | }
204 | }catch(e2){}
205 | try{
206 | response.write( "Error: " + e.stack );
207 | response.end();
208 | console.log("error",e);
209 | }catch(e3){
210 | sys.puts(e3.stack);
211 | }
212 | }
213 | }
214 | }
215 |
216 | function Listener( app ) {
217 | if(typeof app != "function"){
218 | throw new Error("app must be a function");
219 | }
220 | return function( request, response ) {
221 | var connection = request.connection;
222 | request = new Request( request );
223 | var respond = new Response( response, connection );
224 | process.nextTick(function(){
225 | var jsgiResponse;
226 | try {
227 | jsgiResponse = app( request )
228 | } catch( error ) {
229 | jsgiResponse = { status:500, headers:{}, body:[error.stack] };
230 | }
231 | respond( jsgiResponse );
232 | });
233 | }
234 | }
235 | start.Request = Request;
236 | start.Listener = Listener;
237 | start.start = start;
238 |
239 | function start( app, options ) {
240 | app = new Listener( app );
241 | options = options || {};
242 |
243 | var port = options.port || 8080,
244 | http;
245 |
246 | if ( options.ssl ) {
247 | http = require( "https" ).createServer( options.ssl, app ).listen( port );
248 | } else {
249 | http = require( "http" ).createServer( app ).listen( port );
250 | }
251 |
252 | sys.puts( "Server running on port " + port );
253 | return http;
254 | };
255 | module.exports = start;
256 |
--------------------------------------------------------------------------------
/promise.js:
--------------------------------------------------------------------------------
1 |
2 | // Kris Zyp
3 |
4 | // this is based on the CommonJS spec for promises:
5 | // http://wiki.commonjs.org/wiki/Promises
6 | // Includes convenience functions for promises, much of this is taken from Tyler Close's ref_send
7 | // and Kris Kowal's work on promises.
8 | // // MIT License
9 |
10 | // A typical usage:
11 | // A default Promise constructor can be used to create a self-resolving deferred/promise:
12 | // var Promise = require("promise").Promise;
13 | // var promise = new Promise();
14 | // asyncOperation(function(){
15 | // Promise.resolve("succesful result");
16 | // });
17 | // promise -> given to the consumer
18 | //
19 | // A consumer can use the promise
20 | // promise.then(function(result){
21 | // ... when the action is complete this is executed ...
22 | // },
23 | // function(error){
24 | // ... executed when the promise fails
25 | // });
26 | //
27 | // Alternately, a provider can create a deferred and resolve it when it completes an action.
28 | // The deferred object a promise object that provides a separation of consumer and producer to protect
29 | // promises from being fulfilled by untrusted code.
30 | // var defer = require("promise").defer;
31 | // var deferred = defer();
32 | // asyncOperation(function(){
33 | // deferred.resolve("succesful result");
34 | // });
35 | // deferred.promise -> given to the consumer
36 | //
37 | // Another way that a consumer can use the promise (using promise.then is also allowed)
38 | // var when = require("promise").when;
39 | // when(promise,function(result){
40 | // ... when the action is complete this is executed ...
41 | // },
42 | // function(error){
43 | // ... executed when the promise fails
44 | // });
45 | var enqueue = (typeof process !== "undefined" && process.nextTick) || function(func){
46 | func();
47 | };
48 |
49 | var freeze = Object.freeze || function(){};
50 |
51 | /**
52 | * Default constructor that creates a self-resolving Promise. Not all promise implementations
53 | * need to use this constructor.
54 | */
55 | var Promise = function(canceller){
56 | };
57 |
58 | /**
59 | * Promise implementations must provide a "then" function.
60 | */
61 | Promise.prototype.then = function(resolvedCallback, errorCallback, progressCallback){
62 | throw new TypeError("The Promise base class is abstract, this function must be implemented by the Promise implementation");
63 | };
64 |
65 | /**
66 | * If an implementation of a promise supports a concurrency model that allows
67 | * execution to block until the promise is resolved, the wait function may be
68 | * added.
69 | */
70 | /**
71 | * If an implementation of a promise can be cancelled, it may add this function
72 | */
73 | // Promise.prototype.cancel = function(){
74 | // };
75 |
76 | Promise.prototype.get = function(propertyName){
77 | return this.then(function(value){
78 | return value[propertyName];
79 | });
80 | };
81 |
82 | Promise.prototype.put = function(propertyName, value){
83 | return this.then(function(object){
84 | return object[propertyName] = value;
85 | });
86 | };
87 |
88 | Promise.prototype.call = function(functionName /*, args */){
89 | return this.then(function(value){
90 | return value[propertyName].apply(value, Array.prototype.slice.call(arguments, 1));
91 | });
92 | };
93 |
94 | /** Dojo/NodeJS methods*/
95 | Promise.prototype.addCallback = function(callback){
96 | return this.then(callback);
97 | };
98 |
99 | Promise.prototype.addErrback = function(errback){
100 | return this.then(function(){}, errback);
101 | };
102 |
103 | /*Dojo methods*/
104 | Promise.prototype.addBoth = function(callback){
105 | return this.then(callback, callback);
106 | };
107 |
108 | Promise.prototype.addCallbacks = function(callback, errback){
109 | return this.then(callback, errback);
110 | };
111 |
112 | /*NodeJS method*/
113 | Promise.prototype.wait = function(){
114 | return exports.wait(this);
115 | };
116 |
117 | Deferred.prototype = Promise.prototype;
118 | // A deferred provides an API for creating and resolving a promise.
119 | exports.Promise = exports.Deferred = exports.defer = defer;
120 | function defer(){
121 | return new Deferred();
122 | }
123 |
124 | var contextHandler = exports.contextHandler = {};
125 |
126 | function Deferred(canceller){
127 | var result, finished, isError, waiting = [], handled;
128 | var promise = this.promise = new Promise();
129 | var currentContextHandler = contextHandler.getHandler && contextHandler.getHandler();
130 |
131 | function notifyAll(value){
132 | if(finished){
133 | throw new Error("This deferred has already been resolved");
134 | }
135 | result = value;
136 | finished = true;
137 | for(var i = 0; i < waiting.length; i++){
138 | notify(waiting[i]);
139 | }
140 | }
141 | function notify(listener){
142 | var func = (isError ? listener.error : listener.resolved);
143 | if(func){
144 | handled = true;
145 | enqueue(function(){
146 | if(currentContextHandler){
147 | currentContextHandler.resume();
148 | }
149 | try{
150 | var newResult = func(result);
151 | if(newResult && typeof newResult.then === "function"){
152 | newResult.then(listener.deferred.resolve, listener.deferred.reject);
153 | return;
154 | }
155 | listener.deferred.resolve(newResult);
156 | }
157 | catch(e){
158 | listener.deferred.reject(e);
159 | }
160 | finally{
161 | if(currentContextHandler){
162 | currentContextHandler.suspend();
163 | }
164 | }
165 | });
166 | }
167 | else{
168 | if(isError){
169 | if (listener.deferred.reject(result, true)) {
170 | handled = true;
171 | }
172 | }
173 | else{
174 | listener.deferred.resolve.apply(listener.deferred, result);
175 | }
176 | }
177 | }
178 | // calling resolve will resolve the promise
179 | this.resolve = this.callback = this.emitSuccess = function(value){
180 | notifyAll(value);
181 | };
182 |
183 | // calling error will indicate that the promise failed
184 | var reject = this.reject = this.errback = this.emitError = function(error, dontThrow){
185 | isError = true;
186 | notifyAll(error);
187 | if (!dontThrow) {
188 | enqueue(function () {
189 | if (!handled) {
190 | throw error;
191 | }
192 | });
193 | }
194 | return handled;
195 | };
196 |
197 | // call progress to provide updates on the progress on the completion of the promise
198 | this.progress = function(update){
199 | for(var i = 0; i < waiting.length; i++){
200 | var progress = waiting[i].progress;
201 | progress && progress(update);
202 | }
203 | }
204 | // provide the implementation of the promise
205 | this.then = promise.then = function(resolvedCallback, errorCallback, progressCallback){
206 | var returnDeferred = new Deferred(promise.cancel);
207 | var listener = {resolved: resolvedCallback, error: errorCallback, progress: progressCallback, deferred: returnDeferred};
208 | if(finished){
209 | notify(listener);
210 | }
211 | else{
212 | waiting.push(listener);
213 | }
214 | return returnDeferred.promise;
215 | };
216 | var timeout;
217 | if(typeof setTimeout !== "undefined") {
218 | this.timeout = function (ms) {
219 | if (ms === undefined) {
220 | return timeout;
221 | }
222 | timeout = ms;
223 | setTimeout(function () {
224 | if (!finished) {
225 | if (promise.cancel) {
226 | promise.cancel(new Error("timeout"));
227 | }
228 | else {
229 | reject(new Error("timeout"));
230 | }
231 | }
232 | }, ms);
233 | return promise;
234 | };
235 | }
236 |
237 | if(canceller){
238 | this.cancel = promise.cancel = function(){
239 | var error = canceller();
240 | if(!(error instanceof Error)){
241 | error = new Error(error);
242 | }
243 | reject(error);
244 | }
245 | }
246 | freeze(promise);
247 | };
248 |
249 | function perform(value, async, sync){
250 | try{
251 | if(value && typeof value.then === "function"){
252 | value = async(value);
253 | }
254 | else{
255 | value = sync(value);
256 | }
257 | if(value && typeof value.then === "function"){
258 | return value;
259 | }
260 | var deferred = new Deferred();
261 | deferred.resolve(value);
262 | return deferred.promise;
263 | }catch(e){
264 | var deferred = new Deferred();
265 | deferred.reject(e);
266 | return deferred.promise;
267 | }
268 |
269 | }
270 | /**
271 | * Promise manager to make it easier to consume promises
272 | */
273 |
274 | /**
275 | * Registers an observer on a promise.
276 | * @param value promise or value to observe
277 | * @param resolvedCallback function to be called with the resolved value
278 | * @param rejectCallback function to be called with the rejection reason
279 | * @param progressCallback function to be called when progress is made
280 | * @return promise for the return value from the invoked callback
281 | */
282 | exports.whenPromise = function(value, resolvedCallback, rejectCallback, progressCallback){
283 | return perform(value, function(value){
284 | return value.then(resolvedCallback, rejectCallback, progressCallback);
285 | },
286 | function(value){
287 | return resolvedCallback(value);
288 | });
289 | };
290 | /**
291 | * Registers an observer on a promise.
292 | * @param value promise or value to observe
293 | * @param resolvedCallback function to be called with the resolved value
294 | * @param rejectCallback function to be called with the rejection reason
295 | * @param progressCallback function to be called when progress is made
296 | * @return promise for the return value from the invoked callback or the value if it
297 | * is a non-promise value
298 | */
299 | exports.when = function(value, resolvedCallback, rejectCallback, progressCallback){
300 | if(value && typeof value.then === "function"){
301 | return exports.whenPromise(value, resolvedCallback, rejectCallback, progressCallback);
302 | }
303 | return resolvedCallback(value);
304 | };
305 |
306 | /**
307 | * Gets the value of a property in a future turn.
308 | * @param target promise or value for target object
309 | * @param property name of property to get
310 | * @return promise for the property value
311 | */
312 | exports.get = function(target, property){
313 | return perform(target, function(target){
314 | return target.get(property);
315 | },
316 | function(target){
317 | return target[property]
318 | });
319 | };
320 |
321 | /**
322 | * Invokes a method in a future turn.
323 | * @param target promise or value for target object
324 | * @param methodName name of method to invoke
325 | * @param args array of invocation arguments
326 | * @return promise for the return value
327 | */
328 | exports.post = function(target, methodName, args){
329 | return perform(target, function(target){
330 | return target.call(property, args);
331 | },
332 | function(target){
333 | return target[methodName].apply(target, args);
334 | });
335 | };
336 |
337 | /**
338 | * Sets the value of a property in a future turn.
339 | * @param target promise or value for target object
340 | * @param property name of property to set
341 | * @param value new value of property
342 | * @return promise for the return value
343 | */
344 | exports.put = function(target, property, value){
345 | return perform(target, function(target){
346 | return target.put(property, value);
347 | },
348 | function(target){
349 | return target[property] = value;
350 | });
351 | };
352 |
353 |
354 | /**
355 | * Waits for the given promise to finish, blocking (and executing other events)
356 | * if necessary to wait for the promise to finish. If target is not a promise
357 | * it will return the target immediately. If the promise results in an reject,
358 | * that reject will be thrown.
359 | * @param target promise or value to wait for.
360 | * @return the value of the promise;
361 | */
362 | exports.wait = function(target){
363 | if(!queue){
364 | throw new Error("Can not wait, the event-queue module is not available");
365 | }
366 | if(target && typeof target.then === "function"){
367 | var isFinished, isError, result;
368 | target.then(function(value){
369 | isFinished = true;
370 | result = value;
371 | },
372 | function(error){
373 | isFinished = true;
374 | isError = true;
375 | result = error;
376 | });
377 | while(!isFinished){
378 | queue.processNextEvent(true);
379 | }
380 | if(isError){
381 | throw result;
382 | }
383 | return result;
384 | }
385 | else{
386 | return target;
387 | }
388 | };
389 |
390 |
391 |
392 | /**
393 | * Takes an array of promises and returns a promise that is fulfilled once all
394 | * the promises in the array are fulfilled
395 | * @param array The array of promises
396 | * @return the promise that is fulfilled when all the array is fulfilled, resolved to the array of results
397 | */
398 | exports.all = function(array){
399 | var deferred = new Deferred();
400 | if(!(array instanceof Array)){
401 | array = Array.prototype.slice.call(arguments);
402 | }
403 | var fulfilled = 0, length = array.length;
404 | var results = [];
405 | array.forEach(function(promise, index){
406 | exports.when(promise, each, each);
407 | function each(value){
408 | results[index] = value;
409 | fulfilled++;
410 | if(fulfilled === length){
411 | deferred.resolve(results);
412 | }
413 | }
414 | });
415 | return deferred.promise;
416 | };
417 |
418 | /**
419 | * Takes an array of promises and returns a promise that is fulfilled when the first
420 | * promise in the array of promises is fulfilled
421 | * @param array The array of promises
422 | * @return a promise that is fulfilled with the value of the value of first promise to be fulfilled
423 | */
424 | exports.first = function(array){
425 | var deferred = new Deferred();
426 | if(!(array instanceof Array)){
427 | array = Array.prototype.slice.call(arguments);
428 | }
429 | var fulfilled;
430 | array.forEach(function(promise, index){
431 | exports.when(promise, function(value){
432 | if (!fulfilled) {
433 | fulfilled = true;
434 | deferred.resolve(value);
435 | }
436 | },
437 | function(error){
438 | if (!fulfilled) {
439 | fulfilled = true;
440 | deferred.resolve(error);
441 | }
442 | });
443 | });
444 | return deferred.promise;
445 | };
446 |
447 | /**
448 | * Takes an array of asynchronous functions (that return promises) and
449 | * executes them sequentially. Each funtion is called with the return value of the last function
450 | * @param array The array of function
451 | * @param startingValue The value to pass to the first function
452 | * @return the value returned from the last function
453 | */
454 | exports.seq = function(array, startingValue){
455 | array = array.concat(); // make a copy
456 | var deferred = new Deferred();
457 | function next(value){
458 | var nextAction = array.shift();
459 | if(nextAction){
460 | exports.when(nextAction(value), next, deferred.reject);
461 | }
462 | else {
463 | deferred.resolve(value);
464 | }
465 | }
466 | next(startingValue);
467 | return deferred.promise;
468 | };
469 |
470 |
471 | /**
472 | * Delays for a given amount of time and then fulfills the returned promise.
473 | * @param milliseconds The number of milliseconds to delay
474 | * @return A promise that will be fulfilled after the delay
475 | */
476 | if(typeof setTimeout !== "undefined") {
477 | exports.delay = function(milliseconds) {
478 | var deferred = new Deferred();
479 | setTimeout(function(){
480 | deferred.resolve();
481 | }, milliseconds);
482 | return deferred.promise;
483 | };
484 | }
485 |
486 |
487 |
488 | /**
489 | * Runs a function that takes a callback, but returns a Promise instead.
490 | * @param func node compatible async function which takes a callback as its last argument
491 | * @return promise for the return value from the callback from the function
492 | */
493 | exports.execute = function(asyncFunction){
494 | var args = Array.prototype.slice.call(arguments, 1);
495 |
496 | var deferred = new Deferred();
497 | args.push(function(error, result){
498 | if(error) {
499 | deferred.emitError(error);
500 | }
501 | else {
502 | if(arguments.length > 2){
503 | // if there are multiple success values, we return an array
504 | Array.prototype.shift.call(arguments, 1);
505 | deferred.emitSuccess(arguments);
506 | }
507 | else{
508 | deferred.emitSuccess(result);
509 | }
510 | }
511 | });
512 | asyncFunction.apply(this, args);
513 | return deferred.promise;
514 | };
515 |
516 | /**
517 | * Converts a Node async function to a promise returning function
518 | * @param func node compatible async function which takes a callback as its last argument
519 | * @return A function that returns a promise
520 | */
521 | exports.convertNodeAsyncFunction = function(asyncFunction, callbackNotDeclared){
522 | var arity = asyncFunction.length;
523 | if(callbackNotDeclared){
524 | arity++;
525 | }
526 | return function(){
527 | var deferred = new Deferred();
528 | arguments.length = arity;
529 | arguments[arity - 1] = function(error, result){
530 | if(error) {
531 | deferred.emitError(error);
532 | }
533 | else {
534 | if(arguments.length > 2){
535 | // if there are multiple success values, we return an array
536 | Array.prototype.shift.call(arguments, 1);
537 | deferred.emitSuccess(arguments);
538 | }
539 | else{
540 | deferred.emitSuccess(result);
541 | }
542 | }
543 | };
544 | asyncFunction.apply(this, arguments);
545 | return deferred.promise;
546 | };
547 | };
548 |
--------------------------------------------------------------------------------