I'm trying to write a small XHR abstraction as well as learn how to create chainable methods, I am nearly there (I think), but am at a loss as to what to do next, I think my setup is wrong.
What I want to do:
$http.get('file.txt')
.success(function () {
console.log('Success');
})
.error(function () {
console.log('Error');
});
What I've got:
window.$http = {};
$http.get = function (url, cb, data) {
var xhr = {
success: function (callback) {
callback();
return this;
},
error: function (callback) {
callback();
return this;
}
};
// just a test to call the success message
if (window) {
xhr.success.call(xhr);
}
return xhr;
};
I'm having trouble 'wiring' up the success/error messages, can anybody help point me in the right direction? Thanks in advance.
jsFiddle
Your chaining is OK, but you have a error at this line:
if (window) {
xhr.success.call(xhr); // Uncaught TypeError: undefined is not a function
}
So JavaScript breaks and doesn't return xhr. Delete thoses lines and it will work.
success and error are simply functions that store the passed functions into an internal storage. Once the XHR responds, your code should execute all callbacks accordingly, depending on the response status.
Now what you need is an object instance per request that stores its own set of success and error callbacks. Also, success and error methods should return the same instance to allow chaining.
This should set you to the right track:
(function (window) {
window.$http = {};
// An object constructor for your XHR object
function XHRObject(url,data){
// store the request data
this.url = url;
this.data = data;
// The callback storage
this.callbacks = {};
this.init();
}
// Methods
XHRObject.prototype = {
init : function(){
// Actual call here
// Depending on result, execute callbacks
var callbacksToExecute;
if(readyState === 4 && response.status === 200){
callbacksToExecute = this.callbacks.success;
} else {
callbacksToExecute = this.callbacks.error;
}
callbacksToExecute.forEach(function(callback){
callback.call(null);
});
},
success : function(cb){
// Create a success callback array and store the callback
if(this.callbacks.hasOwnProperty('success') this.callbacks.success = [];
this.callbacks.success.push(cb);
// You also need a flag to tell future callbacks to execute immediately
// if the current object has already responded
return this;
},
...
}
// A call to get basically returns an object
$http.get = function (url, data) {
return new XHRObject(url,data);
};
})(this);
I hope you can make something out of this:
window.$http = {};
$http.get = function (url, cb, data) {
var xhr = function(){
return {
success: function (callback) {
callback();
return this;
},
error: function (callback) {
callback();
return this;
}
};
};
return new xhr();
}
$http.get('url','cb','data')
.success(function () {
console.log('Success');
})
.error(function () {
console.log('Error');
});
Edit: I just realized this is basically the same code you wrote, except I'm missing the if(). It seems that test was causing the code to break.
Related
I am trying to wrap my post/get/put/delete calls so that any time they are called, if they fail they will check for expired token, and try again if that is the reason for failure, otherwise just resolve the response/error. Trying to avoid duplicating code four times, but I'm unsure how to resolve from a non-anonymous callback.
factory.post = function (url, data, config) {
var deferred = $q.defer();
$http.post(url, data, config).then(factory.success, factory.fail);
return deferred.promise;
}
factory.success = function (rsp) {
if (rsp) {
//how to resolve parent's promise from from here
}
}
Alternative is to duplicate this 4 times:
.then(function (rsp) {
factory.success(rsp, deferred);
}, function (err) {
factory.fail(err, deferred);
});
One solution might be using bind function.
function sum(a){
return a + this.b;
}
function callFn(cb){
return cb(1);
}
function wrapper(b){
var extra = {b: b};
return callFn(sum.bind(extra));
}
console.log(wrapper(5));
console.log(wrapper(-5));
console.log(wrapper(50));
For your solution check bellow example
factory.post = function (url, data, config) {
var deferred = $q.defer();
$http.post(url, data, config).then(factory.success.bind({deferred: deferred}), factory.fail.bind({deferred: deferred}));
return deferred.promise;
}
factory.success = function (rsp) {
if (rsp) {
this.deferred.resolve(rsp);
//how to resolve parent's promise from from here
}else {
//retry or reject here
}
}
From what I understand, you just want to resolve the deferred object on success and retry on error in case of expired token. Also you probably want to keep a count of number of retries. If so,
Edit - Seems I misunderstood the question. The answer suggested by Atiq should work, or if you are using any functional JS libraries like underscore or Ramdajs, you could use curry function. Using curry function, you can pass some parameters to the function and the function will get executed only after all the parameters are passed. I have modified the code snippet to use curry function from underscorejs.
factory.post = function (url, data, config) {
var deferred = $q.defer();
$http.post(url, data,
config).then(_.curry(factory.success(deferred)),
_.curry(factory.fail(deferred));
return deferred.promise;
}
factory.success = function (deferred, rsp) {
if (rsp) {
//handle resp
deferred.resolve(rsp);
}
}
factory.fail = function(deferred, err){
//handle retry
deferred.reject(err);
}
I want to write the unit test for the factory which have lot chain of promises. Below is my code snippet:
angular.module('myServices',[])
.factory( "myService",
['$q','someOtherService1', 'someOtherService2', 'someOtherService3', 'someOtherService4',
function($q, someOtherService1, someOtherService2, someOtherService3, someOtherService4) {
method1{
method2().then(
function(){ someOtherService3.method3();},
function(error){/*log error;*/}
);
return true;
};
var method2 = function(){
var defer = $q.defer();
var chainPromise = null;
angular.forEach(myObject,function(value, key){
if(chainPromise){
chainPromise = chainPromise.then(
function(){return method4(key, value.data);},
function(error){/*log error*/});
}else{
chainPromise = method4(key, value.data);
}
});
chainPromise.then(
function(){defer.resolve();},
function(error){defer.reject(error);}
);
return defer.promise;
};
function method4(arg1, arg2){
var defer = $q.defer();
someOtherService4.method5(
function(data) {defer.resolve();},
function(error) {defer.reject(error);},
[arg1,arg2]
);
return defer.promise;
};
var method6 = function(){
method1();
};
return{
method6:method6,
method4:method4
};
}]);
To test it, I have created spy object for all the services, but mentioning the problematic one
beforeEach( function() {
someOtherService4Spy = jasmine.createSpyObj('someOtherService4', ['method4']);
someOtherService4Spy.method4.andCallFake(
function(successCallback, errorCallback, data) {
// var deferred = $q.defer();
var error = function (errorCallback) { return error;}
var success = function (successCallback) {
deferred.resolve();
return success;
}
return { success: success, error: error};
}
);
module(function($provide) {
$provide.value('someOtherService4', someOtherService4);
});
inject( function(_myService_, $injector, _$rootScope_,_$q_){
myService = _myService_;
$q = _$q_;
$rootScope = _$rootScope_;
deferred = _$q_.defer();
});
});
it("test method6", function() {
myService.method6();
var expected = expected;
$rootScope.$digest();
expect(someOtherService3.method3.mostRecentCall.args[0]).toEqualXml(expected);
expect(someOtherService4Spy.method4).toHaveBeenCalledWith(jasmine.any(Function), jasmine.any(Function), [arg,arg]);
expect(someOtherService4Spy.method4).toHaveBeenCalledWith(jasmine.any(Function), jasmine.any(Function), [arg,arg]);
});
It is showing error on
expect(someOtherService3.method3.mostRecentCall.args[0]).toEqualXml(expected);
After debugging I found that it is not waiting for any promise to resolve, so method 1 return true, without even executing method3. I even tried with
someOtherService4Spy.method4.andReturn(function(){return deferred.promise;});
But result remain same.
My question is do I need to resolve multiple times ie for each promise. How can I wait till all the promises are executed.
method1 does not return the promise so how would you know the asynchrounous functions it calls are finished. Instead you should return:
return method2().then(
method6 calls asynchronous functions but again does not return a promise (it returns undefined) so how do you know it is finished? You should return:
return method1();
In a test you should mock $q and have it resolve or reject to a value but I can't think of a reason why you would have a asynchronous function that doesn't return anything since you won't know if it failed and when it's done.
Method 2 could be written in a more stable way because it would currently crash if the magically appearing myObject is empty (either {} or []
var method2 = function(){
var defer = $q.defer();
var keys = Object.keys(myObject);
return keys.reduce(
function(acc,item,index){
return acc.then(
function(){return method4(keys[index],myObject[key].data);},
function(err){console.log("error calling method4:",err,key,myObject[key]);}
)
}
,$q.defer().resolve()
)
};
And try not to have magically appearing variables in your function, this could be a global variable but your code does not show where it comes from and I doubt there is a need for this to be scoped outside your function(s) instead of passed to the function(s).
You can learn more about promises here you should understand why a function returns a promise (functions not block) and how the handlers are put on the queue. This would save you a lot of trouble in the future.
I did below modification to get it working. I was missing the handling of request to method5 due to which it was in hang state. Once I handled all the request to method 5 and provided successCallback (alongwith call to digest()), it started working.
someOtherService4Spy.responseArray = {};
someOtherService4Spy.requests = [];
someOtherService4Spy.Method4.andCallFake( function(successCallback, errorCallback, data){
var request = {data:data, successCallback: successCallback, errorCallback: errorCallback};
someOtherService4Spy.requests.push(request);
var error = function(errorCallback) {
request.errorCallback = errorCallback;
}
var success = function(successCallback) {
request.successCallback = successCallback;
return {error: error};
}
return { success: success, error: error};
});
someOtherService4Spy.flush = function() {
while(someOtherService4Spy.requests.length > 0) {
var cachedRequests = someOtherService4Spy.requests;
someOtherService4Spy.requests = [];
cachedRequests.forEach(function (request) {
if (someOtherService4Spy.responseArray[request.data[1]]) {
request.successCallback(someOtherService4Spy.responseArray[request.data[1]]);
} else {
request.errorCallback(undefined);
}
$rootScope.$digest();
});
}
}
Then I modified my test as :
it("test method6", function() {
myService.method6();
var expected = expected;
var dataDict = {data1:"data1", data2:"data2"};
for (var data in dataDict) {
if (dataDict.hasOwnProperty(data)) {
someOtherService4Spy.responseArray[dataDict[data]] = dataDict[data];
}
}
someOtherService4Spy.flush();
expect(someOtherService3.method3.mostRecentCall.args[0]).toEqualXml(expected);
expect(someOtherService4Spy.method4).toHaveBeenCalledWith(jasmine.any(Function), jasmine.any(Function), [arg,arg]);
});
This worked as per my expectation. I was thinking that issue due to chain of promises but when I handled the method5 callback method, it got resolved. I got the idea of flushing of requests as similar thing I was doing for http calls.
I have problem combining javascript callbacks and revealing module pattern.
What I'm trying to do is to return the HTTP response text with the carsData.getCars() function method.
Basically what I want to do is:
return the data from xhr.onreadystatechange function to the private getData function
return the data from getData function to the public getCars function ( or call the getCars function returning a value)
I got it to work with the synchronous AJAX mode, but I'm aware it's not the best practice in javascript development.
I tried to get it to work with callbacks but failed miserably.
Is it even posible to do in javascript?
P.S. I use XMLHttpRequest in Vanilla JS instead of other frameworks for learning purposes.
'use strict';
var carsData = (function() {
var carsElement = document.getElementById('carsListing'),
successHandler = function(data) {
carsElement.innerHTML = data.data;
//return data;
},
dataWrapper = "",
getData = function(callback) {
var url = 'data/cars.json',
xhr = new XMLHttpRequest();
xhr.onreadystatechange = function() {
var status;
var data;
if (xhr.readyState == 4) { // `DONE`
status = xhr.status;
if (status == 200) {
data = JSON.parse(xhr.responseText);
successHandler && successHandler(data);
callback(data);
return data;
}
}
};
xhr.open('get', url, false); // synchronous js
xhr.send();
return xhr.onreadystatechange();
//return 'xx';
}
return {
getCars: function() {
return getData(function(data){
console.log(data); // Object {data: Array[5]}
})
}
}
})();
No. You cannot do it this way. I figured out that is why you typically see results sent to a DOM object. Because they are there waiting for the answer. Your return statement, as counter-intuitive as it seems (assuming you are coming from non-prototype languages), will have already run. It seems like it wouldn't but it has because of the async nature you are aware of. You have to use Promises or you have to have your callback doing something with the data that is "waiting" for the callback data like you did with successdata.
I am overriding a javascript function like this :
(function() {
origFunc = aFunction;
aFunction = function() {
doRequest();
//return origFunc.apply(this);
};
})();
I know that I need to end the function with "return origFunc.apply(this)" in order to execute the original function. However, as I'm executing a request, I have to wait until the request is done. That's why I wrote this function :
doRequest: function()
{
try
{
if(window.XMLHttpRequest)
httpRequest = new XMLHttpRequest();
else if(window.ActiveXObject)
httpRequest = new ActiveXObject("Microsoft.XMLHTTP");
var url = anUrl, self = this;
httpRequest.onreadystatechange = function(data)
{
try
{
if(httpRequest.readyState == 4)
{
if(httpRequest.status == 200)
return origFunc.apply(self);
else if(httpRequest.status != 0 )
alert("Error while executing the request : "+httpRequest.status+"\r\nUrl : "+url);
}
}
catch(e)
{
}
};
httpRequest.open("GET", url);
httpRequest.send();
}
catch(err)
{
alert("Error : "+err);
}
}
As you can guess, the problem is that I can't do the things like that.
Do you know how I could do ?
Here is an example for how to deal with wrapping async functions
// This simply calls the callback with some data after a second
// Could be an AJAX call for example
var doSomethingAsync = function (callback) {
setTimeout(function () {
callback({ some: 'data' });
}, 1000);
};
var fnThatMakesAsyncCall = function () {
// From the outside there is no way to change this callback
// But what if we need to intercept the async function to change the data given to it, or monitor it?
// Then we'd have to wrap the async function to wrap the callback.
var callback = function (data) {
console.log('Original', data);
};
doSomethingAsync(callback);
};
// Function to wrap another function and return the wrapper
var wrapFn = function (fn) {
// Create the wrapped function.
// Notice how it has the same signature with `callback` as the first argument
var wrapped = function (callback) {
// Here we get the original callback passed in
// We will instead wrap that too and call the original function with our new callback
var newCb = function (data) {
// This will run once the async call is complete
// We will as an example mutate the data in the return data of the callback
data.some = 'Wrapped it';
// Call the original callback with the changed data
callback.call(this, data);
};
// Run the function we wrap with the new callback we supply
fn.call(this, newCb);
};
// Return wrapped function
return wrapped;
};
// Will log Original {some: "data"}
fnThatMakesAsyncCall();
doSomethingAsync = wrapFn(doSomethingAsync);
// Will log Original {some: "Wrapped it"}
fnThatMakesAsyncCall();
I have make some functions to retrieve data using the Github API. I have the callbacks in place to get the data but I am sure how to understand where a function exits and when I stops modifying things.
For example in the code below, in the first function, when the AJAX call is successful, the callback is executed in the second function where the data is manipulated. Does that mean the the return in the first function is not needed or used? And in the second function is the data used and pushed to the array and then the array returned or is it the other way around where the (empty) array is returned and then the callback does its thing.
I am ultimately trying to get the data from the callback into an object and return that filled object from the parent function.
function makeAJAXCall(hash, cb) {
var returnedJSON, cb = cb, hash = hash;
$.ajax({
accepts: 'application/vnd.github-blob.raw',
dataType: 'jsonp',
url: hash,
success: function (json) {
console.info(json);
returnedJSON = json;
// Time for callback to be executed
if (cb) {
cb(json);
}
},
error: function (error) {
console.error(error);
// an error happened, check it out.
throw error;
}
});
return returnedJSON;
}
function parseBlob(hash) {
var objectedJSON, objectList = [], i;
objectedJSON = makeAJAXCall(hash, function (objectedJSON) { // no loop as only one entry
objectList.push(objectedJSON.content);
});
return objectList;
}
function walkTree(hash) {
var objectedJSON, objectList = [], i, entry;
var hash = 'https://api.github.com/repos/myAccountName/repo/git/trees/' + hash;
objectedJSON = makeAJAXCall(hash, function (objectedJSON) {
for (i = 0; i < objectedJSON.data.tree.length; i += 1) {
entry = objectedJSON.data.tree[i];
console.debug(entry);
if (entry.type === 'blob') {
if (entry.path.slice(-4) === '.svg') { // we only want the svg images not the ignore file and README etc
console.info(entry.path)
objectList.push(parseBlob(entry.url));
}
} else if (entry.type === 'tree') {
objectList.push(walkTree(entry.sha));
}
}
});
console.info(objectList);
return objectList;
}
$(document).ready(function () {
var objects = walkTree('master', function () { // master to start at the top and work our way down
console.info(objects);
});
});
Here you are making an AJAX call A refers to asynchronous, ie your success/error callback will be executed asynchronously.
makeAJAXCall will return before executing success/error of $ajax.
so the objectedJSON = makeAJAXCall will return you undefined
function makeAJAXCall(hash, cb) {
$.ajax({
accepts: 'application/vnd.github-blob.raw',
dataType: 'jsonp',
url: hash,
success: function (json) {
// this function will be executed after getting response from server
//ie Asynchronously
//here cb passed from the makeAjaxCall exist in the closure scope
if (cb) {
cb(json);
}
},
error: function (error) {
console.error(error);
// an error happened, check it out.
throw error;
}
});
}
Now when you call makeAjaxCall the callback function you are passing will exist in the closure scope of $.ajax and will be executed on success of server response
makeAJAXCall(hash, function (objectedJSON) {
//objectJSON contains the response from server
// do all your operations using server response over here or assign it to a global variable
});
check below links
https://developer.mozilla.org/en/JavaScript/Guide/Closures
https://mikewest.org/2009/05/asynchronous-execution-javascript-and-you
or you can make your ajax call in sync using async:false which is highly not recommended
function makeAJAXCall(hash, cb) {
var returnedJSON;
$.ajax({
accepts: 'application/vnd.github-blob.raw',
dataType: 'json',
async : false, //this will make it in sync
url: hash,
success: function (json) {
console.info(json);
returnedJSON = json;
//now makeAJAXCall will wait for success to complete and it will return only after executing success/error
// Time for callback to be executed
if (cb) {
cb(json);
}
},
error: function (error) {
console.error(error);
// an error happened, check it out.
throw error;
}
});
//will wait for success/error before returning
return returnedJSON;
}
In the above case your code will work
function makeAJAXCall(hash, cb) {
var returnedJSON, cb = cb, hash = hash;
return $.ajax({
accepts: 'application/vnd.github-blob.raw',
dataType: 'jsonp',
url: hash,
success: function (json) {
console.info(json);
returnedJSON = json;
// Time for callback to be executed
if (cb) {
cb(json);
}
},
error: function (error) {
console.error(error);
// an error happened, check it out.
throw error;
}
});
}
function parseBlob(hash) {
var objectedJSON, objectList = [], i;
objectedJSON = makeAJAXCall(hash, function (objectedJSON) { // no loop as only one entry
objectList.push(objectedJSON.content);
});
return objectList;
}
function walkTree(hash) {
var objectedJSON, objectList = [], i, entry;
var hash = 'https://api.github.com/repos/myAccountName/repo/git/trees/' + hash;
objectedJSON = $.when(maxAJAXCall)
.then(function(){
//Write the callback
});
Use $.when().then() to call ajax and manage the callbacks better.
.When