React/Redux n00b here :) - working with a crappy API that doesn't correctly return error codes (returns 200 even when end point is down), therefore is messing up my Ajax calls. Owner of the API will not able to correct this soon enough, so I have to work around it for now.
I'm currently checking each success with something like this (using lodash):
success: function(data) {
// check if error is REALLY an error
if (_.isUndefined(data.error) || _.isNull(data.error)) {
if (data.id) data.sessionId = data.id;
if (data.alias) data.alias = data.alias;
resolve(data || {});
} else {
reject(data); // this is an error
}
}
I want to move this into it's own function so that I can use it with any action that performs an Ajax call, but I'm not sure where to include this.
Should this type of function map to state (hence, treat it like an action and build a reducer) or should this be something generic outside of Redux and throw in main.js for example?
You can dispatch different actions depending on if the promise was resolved or not. The simplest way would be something like this:
function onSuccess(data) {
return {
type: "FETCH_THING_SUCCESS",
thing: data
};
}
function onError(error) {
return {
type: "FETCH_THING_ERROR",
error: error
};
}
function fetchThing(dispatch, id) {
// the ajax call that returns the promise
fetcher(id)
.then(function(data){
dispatch(onSuccess(data));
})
.catch(function(error) {
dispatch(onError(error));
});
}
Heres some more documentation how to do this kind of thing...
Related
I'm working on switching the code to rxjs
here is my original code.
userAuth$: BehaviorSubject<ArticleInfoRes>;
async loadArticleList(articleId: number) {
try {
const data = await this.articleApi.loadArticleList(articleId);
this.userAuth$.next(data);
return data;
} catch (error) {
return error;
}
}
Subsequently, an attempt was made to convert to rxjs, but the value was not delivered properly. A value or error must be passed to the location where the loadArticleList function is used. Please tell me what's wrong
This is the code I tried to convert.
userAuth$: BehaviorSubject<ArticleInfoRes>;
loadArticleList(articleId: number) {
from(this.articleApi.loadArticleList(articleId)).subscribe(
data => {
this.userAuth$.next(data);
return data;
},
error => {
return error;
}
)
}
A value or error must be passed to the location where the loadArticleList function is used. Please tell me what's wrong
To answer your question directly, the reason why the result (i.e., value or error) isn't passed to where you use loadArticleList() is because your RxJS pipeline is not built properly. For one, adding return data; and return error; in your subscription handlers won't actually return data nor error.
To get the result, you might want to "trickle down" (streamline) that result further down the RxJS pipeline, so that subscription to that result happens in the location you speak of, to where loadArticleList() is used. This translates to moving your call to .subscribe() outside of loadArticleList(), not inside.
So, here's a proper RxJS revision:
import { tap } from "rxjs/operators";
userAuth$: BehaviorSubject<ArticleInfoRes>;
loadArticleList(articleId: number) {
return from(this.articleApi.loadArticleList(articleId)).pipe(
tap((data) => this.userAuth$.next(data)) // 👈 tap into the result to update the next userAuth$
);
}
Using the tap RxJS operator, we can grab the result of loadAarticleList(articleId), process it according to your callback function inside tap (in this case, simply update the next value of userAuth$), and then with that same grabbed result, unmodified, pass it on to whoever subscribes to loadArticleList().
Finally, to actually "pass" data or errorto the location where the loadArticleList function is used, simply subscribe there.
// in your component's .ts somwehere, probably
loadArticleList(123).subscribe(
data => { /* do as you want with the result */ },
error => { /* add your error-handling logic here */ }
);
I'm trying to learn pomise and I used code which I found in Internet... I don't understand everything. It looks for me very nasty but it works...
I initialized promise
function initialize(city) {
var options = {
url: 'https://api.weatherbit.io/v2.0//forecast/',
headers: {
'User-Agent': 'request'
}
};
return new Promise(function(resolve, reject) {
request.get(options, function(err, resp, body) {
if (err) {
reject(err);
} else {
resolve(JSON.parse(body));
}
})
})
}
I don't understand why I need to put return after initializePromise. Is it possible to refactor the code without return?
var initializePromise = initialize("Berlin");
return initializePromise.then(function(result) {
weather = result;
var rain = result["data"][0]["precip"];
console.log(rain);
}, function(err) {
console.log(err);
})
This all depends upon what you want to do. If you wrote this version, which is slightly altered from your original but functionally the same:
function f1(city) {
return initialize(city).then(function(result) {
const weather = result
const rain = result["data"][0]["precip"];
console.log(rain)
}, function(err) {
console.log(err)
})
}
then when you call
f1('Berlin')
you would request the Berlin result from the server, When the server responds, you would either pass to console.log the error received from the request or turn the returned body into a JS object, extract the appropriate precip property from it, and log that to the console. The resulting Promise value returned from f1 is useless, and the weather variable is unused and unusable.
If you want to log that precipitation, but still keep a useful return value, you can write:
function f2(city) {
return initialize(city).then(function(result) {
const weather = result
const rain = result["data"][0]["precip"];
console.log(rain)
return weather // *** NOTE new line here ***
}, function(err) {
console.log(err)
})
}
This time calling with Berlin (and ignoring the error case from now on), you would log the precipitation returned, but also return a Promise for the whole Berlin weather node. That means you can still do this:
f2('Berlin')
to log Berlin's first precipitation value, but that now returns a useful value, so you could do
f2('Berlin').then(console.log)
to do that same logging, and then log the entire Berlin result.
Or you could do
f2('Berlin').then(function(weather) {
// do something useful with `weather` here.
}, errorHandler)
But now note the cleanup that is available. First of all the rain variable in f2 is only used on the next line, and the weather one is simply a reference to the original result argument. So we can simplify it this way:
function f3(city) {
return initialize(city).then(function(result) {
console.log(result["data"][0]["precip"])
return result
}, function(err) {
console.log(err)
})
}
This does the same thing more simply. But now there is a much more important simplification. It's quite possible that we don't need this function at all! If we have an error handler of some sort (even if it's just console.err), and we already have a function that does most of the weather handling we want, then instead of
f3('Berlin').then(function(weather) {
// do something useful with `weather` here.
}, errorHandler)
we can add the logging line from f3 into this first callbak, and get the same result by calling directly to initialize:
initialize('Berlin').then(function(weather) {
console.log(weather["data"][0]["precip"])
// do something useful with `weather` here.
}, errorHandler)
The reason this works is because initialize returns the result of calling then on the Promise, and then f2 and f3 also return either an altered value or the original one, keeping a Promise chain intact.
I would suggest that if you're in doubt you return something in any of these situations. It makes it much easier to continue working with values.
I'm using the popular node library, got, to make simple GET requests to a JSON API.
I have a function that abstracts the request, like so:
function performRequest(url) {
got(url, {
json: true
}).then(function (response) {
return formatResponse(response.body);
}).catch(function (error) {
console.log(error.response.body);
});
}
formatResponse is a simple synchronous method that modifies the JSON returned from the API.
I would like to be able to call performRequest from another function and then use the return value (once resolved). Currently, as performRequest is not recognized as an async method, my code is calling it and then proceeding immediately.
function myBigFunction() {
var url = composeUrl();
var res = performRequest(url);
doMoreStuffWithResponse(res);
}
I know that I need to utilize a Promise, however, I'm always unclear as to how to use a Promise in conjunction with a built-in library function that is already using a Promise (like in this case).
I'm also completely open to the possibility that I'm going about this all wrong. In that case, I would appreciate some redirection.
Thank you for your time.
Understand what a Promise is. Its a value, you can treat it as such. In order to "read" the value, you pass a function to the Promise's then method. You don't need myBigFunction. Anything you want to run after the Promise resolves just needs to be passed to then:
var req = performRequest(composeURL());
req.then(doStuffWithResponse);
Now, I don't particularly care for this way although I do it fairly often. I prefer to have functions that take promises and invoke their then method:
var takesAPromise = function(p) {
return p.then(/* does stuff */);
};
Note that it returns the Promise of the completed task. But what I like even better is this ES6 one-liner:
let wrap = f => p => p.then(val => f.call(null, val));
Now you can wrap arbitrary functions to take Promises as input and return them as output. If Promises were a monad, this would be their bind function. Making it work seamlessly with functions of arbitrary arity is left as an exercise to the reader.
You'll always want to return a promise from your functions:
function performRequest(url) {
return got(url, {
//^^^^^^
json: true
}).then(function(response) {
return formatResponse(response.body);
}, function(error) {
throw new Error(error.response.body);
});
}
With this, you can wait for the result in your big functions using another then:
function myBigFunction() {
var url = composeUrl();
var promise = performRequest(url);
return promise.then(function(res) {
return doMoreStuffWithResponse(res);
});
}
or in short
function myBigFunction() {
return performRequest(composeUrl()).then(doMoreStuffWithResponse);
}
so that you can call it like
myBigFunction().catch(function(error) {
console.log(error.message);
});
I am still learning promises in angular and have this bit of code where I am making a "GET" request two times. I want to run one get request before calling the other. This is working fine, but how would I handle errors here? If I get an error for my first GET request how do I find out what that error is and prevent my code from calling the second GET request? Examples with my code would be most helpful.
apiServices.login = function(user,password,callback) {
$http.get("http://magainteractive.com/prototypes/cisco-ima-dashboard/cms/web/api/login/login/?username="+user+"&password="+password+"")
.then(function(contentResponse){
resultsObject.content = contentResponse;
return $http.get("http://magainteractive.com/prototypes/cisco-ima-dashboard/cms/web/api/data/list/");
})
.then(function(dataResponse){
resultsObject.reports = dataResponse;
resultsObject.success = 1;
console.log(resultsObject);
callback(resultsObject);
apiServices.useData(resultsObject);
});
}
dummyData.login(username, password, function (dataStatus) {
if (dataStatus.success = 1) {
$rootScope.loggedIn = true;
$rootScope.selectedDashboard = 1;
} else {
console.log("Error");
}
});
I would do things slightly different from Lucas, I prefer chaining a catch block( basically it would act like the synchrounous try...catch block we use) rather than adding an error callback function so code would be like:
return $http.get(url1)
.then(function(result){
resultsObject.url1 = result;
return $http.get(url2);
}).then(function(result){
resultsObject.url2 = result;
return resultsObject;
}).catch(function(error){
// handle error.
});
P.S: most of your code is fine, but I am not really sure why you have that callback(resultsObject);, when you are using promises, callbacks are redundant, you could just return the promise chain $http.get...
You can pass a second parameter in the first callback handling. This will trigger if there's an error in the request, then you can handle it however you want:
$http({
method: 'GET',
url: '/someUrl'
}).then(function successCallback(response) {
// this callback will be called asynchronously
// when the response is available
}, function errorCallback(response) {
// called asynchronously if an error occurs
// or server returns response with an error status.
});
Or in your coding:
$http.get('/someUrl').then(successCallback, errorCallback);
More information here
Your code would look like:
$http.get("http://magainteractive.com/prototypes/cisco-ima-dashboard/cms/web/api/login/login/?username="+user+"&password="+password+"")
.then(function(contentResponse){
resultsObject.content = contentResponse;
return $http.get("http://magainteractive.com/prototypes/cisco-ima-dashboard/cms/web/api/data/list/");
}, function(error){
//HANDLE ERROR HERE
})
.then(function(dataResponse){
resultsObject.reports = dataResponse;
resultsObject.success = 1;
console.log(resultsObject);
callback(resultsObject);
apiServices.useData(resultsObject);
});
I'm new to AngularJS and Breeze. I'm trying to save changes and have a problem with that. Here's my code:
In controller:
function update() {
vm.loading = true;
return datacontext.saveSettings().then(function () {
vm.loading = false; // never gets called
}).fail(function (data) {
vm.loading = false; // never gets called
});
}
In datacontext:
function saveSettings() {
if (manager.hasChanges()) {
manager.saveChanges() // breaks here if there are changes
.then(saveSucceeded)
.fail(saveFailed)
.catch(saveFailed);
} else {
log("Nothing to save");
return false;
};
}
The error is thrown in angular.js and it's very unhelpful TypeError: undefined is not a function I guess there is something simple I'm missing here, but can't figure out what is it.
Want to note that it does send correct data to SaveChanges method on server, but the error is thrown before any response from the server received. After the response is received it throws another error TypeError: Cannot read property 'map' of undefined but it might be related to the fact the response I return is invalid. I haven't got to that part yet.
Can anyone anyone help with it? I'm lost here.
UPDATE
Here is how I construct my dataservice and manager:
var serviceName = "http://admin.localhost:33333/api/breeze/"; //
var ds = new breeze.DataService({
serviceName: serviceName,
hasServerMetadata: false,
useJsonp: true,
jsonResultsAdapter: jsonResultsAdapter
});
var manager = new breeze.EntityManager({ dataService: ds });
model.initialize(manager.metadataStore);
Two problems:
Your datacontext method does not return a promise so the caller cannot find anything to hang the then or fail call on.
You should be callingcatch, not fail
1. Return a promise
Your saveSettings method did not return a result in the success case so it must fail. Your method must also return a promise in the fail case ... or it will also fail.
And while I'm here, since there is no real difference between your success and fail case, you might as well move the vm.loading toggle to the finally clause.
Try this instead:
function update() {
vm.loading = true;
return datacontext.saveSettings()
.then(function () {
// .. success handling if you have any
})
.catch(function (data) {
// .. fail handling if you have any
})
.finally(funtion() {
vm.loading = false; // turn it off regardless
});
}
And now the dataContext ... notice the two return statements return a promise.
function saveSettings() {
if (manager.hasChanges()) {
return manager.saveChanges()
.then(saveSucceeded)
.catch(saveFailed);
} else {
log("Nothing to save");
// WHY ARE YOU FAILING WHEN THERE IS NOTHING TO SAVE?
// Breeze will handle this gracefully without your help
return breeze.Q.reject(new Error('Nothing to save'));
};
}
2. Use catch
I assume you have configured Breeze to use Angular's $q for promises (you should be using the "breeze.angular" service and have injected "breeze" somewhere).
$q does not have a fail method! The equivalent is catch. For some reason you have both attached to your query. You'll get the ReferenceError exception immediately, before the server has a chance to respond ... although it will launch the request and you will get a callback from the server too.
Try just:
return manager.saveChanges()
.then(saveSucceeded)
.catch(saveFailed);
You see many Breeze examples that call fail and fin. These are "Q.js" methods; "Q.js" is an alternative promise library - one used by Breeze/KnockOut apps and it was the basis for Angular's $q.
Both "Q.js" and $q support the now-standard catch and finally promise methods. We're slowly migrating our example code to this standard. There is a lot of old fail/finally code around in different venues. It will take time.
Sorry for the confusion.
Update savesetting function like below to return Promise.
function saveSettings() {
if (manager.hasChanges()) {
return manager.saveChanges(); // return promise
} else {
log("Nothing to save");
return false;
};
}
Then you can call then and fail in update function like following.
function update() {
vm.loading = true;
return datacontext.saveSettings().then(function () {
vm.loading = false; // never gets called
}).fail(function (data) {
vm.loading = false; // never gets called
});
}