Skip to content

Commit

Permalink
Add client side promise support
Browse files Browse the repository at this point in the history
  • Loading branch information
Vijar committed Sep 3, 2015
1 parent 265e9d1 commit 022076d
Show file tree
Hide file tree
Showing 3 changed files with 123 additions and 30 deletions.
69 changes: 47 additions & 22 deletions libs/fetcher.client.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ var DEFAULT_XHR_TIMEOUT = 3000;
var MAX_URI_LEN = 2048;
var OP_READ = 'read';
var defaultConstructGetUri = require('./util/defaultConstructGetUri');
var Promise = global.Promise || require('es6-promise').Promise;

function parseResponse(response) {
if (response && response.responseText) {
Expand Down Expand Up @@ -108,8 +109,32 @@ Request.prototype.clientConfig = function (config) {
* @async
*/
Request.prototype.end = function (callback) {
var clientConfig = this._clientConfig;
var callback = callback || lodash.noop;
var self = this;
var promise = new Promise(function (resolve, reject) {
debug('Executing request %s.%s with params %o and body %o', self.resource, self.operation, self._params, self._body);
setImmediate(executeRequest, self, resolve, reject);
});

if (callback) {
promise.then(function (data) {
setImmediate(callback, null, data);
}, function (err) {
setImmediate(callback, err);
});
} else {
return promise;
}
};

/**
* Execute and resolve/reject this fetcher request
* @method executeRequest
* @param {Object} request Request instance object
* @param {Function} resolve function to call when request fulfilled
* @param {Function} reject function to call when request rejected
*/
function executeRequest (request, resolve, reject) {
var clientConfig = request._clientConfig;
var use_post;
var allow_retry_post;
var uri = clientConfig.uri;
Expand All @@ -118,19 +143,19 @@ Request.prototype.end = function (callback) {
var data;

if (!uri) {
uri = clientConfig.cors ? this.options.corsPath : this.options.xhrPath;
uri = clientConfig.cors ? request.options.corsPath : request.options.xhrPath;
}

use_post = this.operation !== OP_READ || clientConfig.post_for_read;
use_post = request.operation !== OP_READ || clientConfig.post_for_read;
// We use GET request by default for READ operation, but you can override that behavior
// by specifying {post_for_read: true} in your request's clientConfig
if (!use_post) {
var getUriFn = lodash.isFunction(clientConfig.constructGetUri) ? clientConfig.constructGetUri : defaultConstructGetUri;
var get_uri = getUriFn.call(this, uri, this.resource, this._params, clientConfig, this.options.context);
var get_uri = getUriFn.call(request, uri, request.resource, request._params, clientConfig, request.options.context);
if (!get_uri) {
// If a custom getUriFn returns falsy value, we should run defaultConstructGetUri
// TODO: Add test for this fallback
get_uri = defaultConstructGetUri.call(this, uri, this.resource, this._params, clientConfig, this.options.context);
get_uri = defaultConstructGetUri.call(request, uri, request.resource, request._params, clientConfig, request.options.context);
}
if (get_uri.length <= MAX_URI_LEN) {
uri = get_uri;
Expand All @@ -140,43 +165,43 @@ Request.prototype.end = function (callback) {
}

if (!use_post) {
return REST.get(uri, {}, lodash.merge({xhrTimeout: this.options.xhrTimeout}, clientConfig), function getDone(err, response) {
return REST.get(uri, {}, lodash.merge({xhrTimeout: request.options.xhrTimeout}, clientConfig), function getDone(err, response) {
if (err) {
debug('Syncing ' + this.resource + ' failed: statusCode=' + err.statusCode, 'info');
return callback(err);
debug('Syncing ' + request.resource + ' failed: statusCode=' + err.statusCode, 'info');
return reject(err);
}
callback(null, parseResponse(response));
resolve(parseResponse(response));
});
}

// individual request is also normalized into a request hash to pass to api
requests = {};
requests[DEFAULT_GUID] = {
resource: this.resource,
operation: this.operation,
params: this._params
resource: request.resource,
operation: request.operation,
params: request._params
};
if (this._body) {
requests[DEFAULT_GUID].body = this._body;
if (request._body) {
requests[DEFAULT_GUID].body = request._body;
}
data = {
requests: requests,
context: this.options.context
context: request.options.context
}; // TODO: remove. leave here for now for backward compatibility
uri = this._constructGroupUri(uri);
allow_retry_post = (this.operation === OP_READ);
REST.post(uri, {}, data, lodash.merge({unsafeAllowRetry: allow_retry_post, xhrTimeout: this.options.xhrTimeout}, clientConfig), function postDone(err, response) {
uri = request._constructGroupUri(uri);
allow_retry_post = (request.operation === OP_READ);
REST.post(uri, {}, data, lodash.merge({unsafeAllowRetry: allow_retry_post, xhrTimeout: request.options.xhrTimeout}, clientConfig), function postDone(err, response) {
if (err) {
debug('Syncing ' + this.resource + ' failed: statusCode=' + err.statusCode, 'info');
return callback(err);
debug('Syncing ' + request.resource + ' failed: statusCode=' + err.statusCode, 'info');
return reject(err);
}
var result = parseResponse(response);
if (result) {
result = result[DEFAULT_GUID] || {};
} else {
result = {};
}
callback(null, result.data);
resolve(result.data);
});
};

Expand Down
5 changes: 3 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
],
"dependencies": {
"debug": "^2.0.0",
"es6-promise": "^3.0.2",
"fumble": "^0.1.0",
"lodash": "^3.3.0",
"xhr": "^2.0.0"
Expand All @@ -36,8 +37,8 @@
"mocha": "^2.0.1",
"mockery": "^1.4.0",
"pre-commit": "^1.0.0",
"qs": "^3.0.0",
"request": "^2.55.0",
"qs": "^4.0.0",
"request": "^2.61.0",
"supertest": "^1.0.1"
},
"jshintConfig": {
Expand Down
79 changes: 73 additions & 6 deletions tests/unit/libs/fetcher.client.js
Original file line number Diff line number Diff line change
Expand Up @@ -124,25 +124,92 @@ describe('Client Fetcher', function () {
};
var body = { stuff: 'is'};
var config = {};
var callback = function(operation, done) {
return function(err, data) {
var callback = function (operation, done) {
return function (err, data) {
if (err){
done(err);
}
expect(data.operation).to.exist;
expect(data.operation.name).to.equal(operation);
expect(data.operation.success).to.equal(true);
expect(data.operation.success).to.be.true;
expect(data.args).to.exist;
expect(data.args.resource).to.equal(resource);
expect(data.args.params).to.eql(params);
done();
};
};
var resolve = function (operation, done) {
return function (data) {
try {
expect(data).to.exist;
expect(data.operation).to.exist;
expect(data.operation.name).to.equal(operation);
expect(data.operation.success).to.be.true;
expect(data.args).to.exist;
expect(data.args.resource).to.equal(resource);
expect(data.args.params).to.eql(params);
} catch (e) {
done(e);
return;
}
done();
};
};
var reject = function (operation, done) {
return function (err) {
done(err);
};
};
describe('should work superagent style', function (done) {
testCrud(it, resource, params, body, config, callback);
it('should throw if no resource is given', function () {
expect(fetcher.read).to.throw('Resource is required for a fetcher request');
describe('with callbacks', function () {
testCrud(it, resource, params, body, config, callback);
it('should throw if no resource is given', function () {
expect(fetcher.read).to.throw('Resource is required for a fetcher request');
});
});
describe('with Promises', function () {
it('should handle CREATE', function (done) {
var operation = 'create';
fetcher
[operation](resource)
.params(params)
.body(body)
.clientConfig(config)
.end()
.then(resolve(operation, done), reject(operation, done));
});
it('should handle READ', function (done) {
var operation = 'read';
fetcher
[operation](resource)
.params(params)
.clientConfig(config)
.end()
.then(resolve(operation, done), reject(operation, done));
});
it('should handle UPDATE', function (done) {
var operation = 'update';
fetcher
[operation](resource)
.params(params)
.body(body)
.clientConfig(config)
.end()
.then(resolve(operation, done), reject(operation, done));
});
it('should handle DELETE', function (done) {
var operation = 'delete';
fetcher
[operation](resource)
.params(params)
.clientConfig(config)
.end()
.then(resolve(operation, done), reject(operation, done));
});
it('should throw if no resource is given', function () {
expect(fetcher.read).to.throw('Resource is required for a fetcher request');
});
})
});
describe('should be backwards compatible', function (done) {
// with config
Expand Down

0 comments on commit 022076d

Please sign in to comment.