I have a code where for each of the ids I am making an ajax request and processing them as results come in. Here is a simple replica of my actual code and jsfiddle:
var ids = [1,2,3,4,5,6];
var ids$ = (id) => {
return Observable.of(id);
};
var loadIds$ = (id) => {
if(id == 4) return xhr('/echo/jsoneee/', {id: id});
return xhr('/echo/json/', {id: id});
};
Observable.from(ids)
.concatMap(id => Observable.forkJoin(ids$(id), loadIds$(id)))
.catch((err, caught) => {
//get the id for which loadIds failed
//send the id to subscribe() and continue the subscription
return Observable.empty();
})
.subscribe(result => console.log(result))
But now I need to modify the code so that if an error occurs I will have to get the id for which the ajax request failed and then just continue the subscription like nothing happened. I have not been able to do this yet. Help is really appreciated.
I think you can simplify this significantly by emitting correct values right in Observable.create(...):
function xhr(url, json) {
return Observable.create(function (observer) {
$.ajax({
url: url,
data: JSON.stringify(json),
success: function (response) {
observer.next([json.id, json]);
},
error: function (jqXHR, status, error) {
observer.next([json.id]); // <== Notice this
},
complete: function () {
observer.complete();
}
});
});
}
var ids = [1,2,3,4,5,6];
var ids$ = (id) => {
return Observable.of(id);
};
var loadIds$ = (id) => {
if(id == 4) return xhr('/echo/jsoneee/', {id: id});
return xhr('/echo/json/', {id: id});
};
Observable.from(ids)
.concatMap(id => loadIds$(id))
.subscribe(result => console.log(result));
This way you can avoid forkJoin() completely. Also be aware that catch() operator automatically unsubscribes from its source. This operator is intended to continue with another Observable so it's now very useful in cases such as yours.
You could of course use:
.catch((error, caught) => {
return caught;
})
This however causes resubscription and thus reemission of all values from the beginning which is usually undesired.
There's also onErrorResumeNext() operator that simply ignores the errors but that's probably not what you want.
See demo: https://jsfiddle.net/4f1zmeyd/1/
A slightly similar question: get new ticket then retry first request
Related
I am learning javascript, trying out some things, but currently having an issue.
I am trying to work with an API. Essentially - the API endpoint returns an array and within the indexes there is the data in identical structure across each index element.
So for example, it looks like this:
0-1000
Then when I expand it out, they're like this:
0:
param_1: 1
param_2: 2
1:
param_1: 3
param_2: 4
2:
param_1: 5
param_2: 6
... and so on.
The data structure, as shown by the DevTools:
The problem is - when I run the below code, I can print to console the top level array which returns the arrays named 0, 1, 2, 3, etc. OR I can also obviously give an array element and say console.log(array[45]).
But what I want to do is do a foreach to remove the top level index if this makes sense and just return the same parameter from each sub-array element. So for example, just return each and every param_1 into a new array or to a console log.
My problem is, I've tried many things and I do not have enough understanding of all the complexities to make this work. I've tried a .map, or a for-each, but I think there are issues with promises not being fulfilled, etc. I also don't want to do a for-each on the whole of the API fetch which returns the original array.
The below code is what I'm working with and what works, and I will add in pseudocode further down what my intention is.
Working:
export const getWeatherData = (location) => {
fetch(url, opts)
.then(function (response) {
document.querySelector(".loading").textContent = "";
return response.json();
})
.then(function (response) {
return response;
})
.then(function (response) {
const weatherData = {
name: response.data.blah
};
console.log(weatherData.name);
return weatherData;
})
.then(function (weatherData) {
displayWeatherData(weatherData);
})
.catch(function (err) {
err = displayError();
});
};
Pseudocode:
export const getWeatherData = (location) => {
fetch(url, opts)
.then(function (response) {
document.querySelector(".loading").textContent = "";
return response.json();
})
.then(function (response) {
return response;
})
.then(function (response) {
for each (item in response){
add item.data.blah.param_1 to newarray.
}
const weatherData = newarray
console.log(weatherData);
return weatherData;
})
.then(function (weatherData) {
displayWeatherData(weatherData);
})
.catch(function (err) {
err = displayError();
});
};
I hope this makes sense, any questions, etc. or if you need clarity I will respond ASAP.
const newarray = response.map((item) => {
return item.data.blah.param_1;
});
This sould work for your problem. If there is sub arrays u can use forEach like this
const newarray = [];
response.forEach((item) => {
item.data.blah.forEach((subItem) => {
newarray.push(subItem.param_1);
});
});
console.log(newarray);
I'm making a cart in a e-commerce website.
This script delete one piece of product if user clicked on a button
$(".minusCart").click(function () {
var id = $(this).attr("data-id");
$.post("/cart/deleteOneAjax/" + id, {}, function (data) {
if (!data) {
location.reload();
} else {
$("#id-count" + id).val(data);
}
});
$.post("/cart/countAjax/" + id, {}, function (data) {
$("#cart-count").html(data);
});
$.post("/cart/productPriceAjax/" + id, {}, function (data) {
$("#product-price-cart" + id).html(data);
});
$.post("/cart/totalPriceAjax/" + id, {}, function (data) {
$(".total-price-cart").html(data);
});
});
So steps this script should do are:
Delete one piece of product
Calculate how many pieces are left
Calculate a sum for each product (quantity of product * price)
Calculate a sum for the whole cart
So the order is really important. But because those are asynchronous requests, responses could be sent back in a different order.
Because of that sometimes I get response for /cart/deleteOneAjax/ the last and all other data is not calculated properly.
What do I do in order to solve it?
ajax is asynchronous function, which mean the JS is not necessary to process your code from line 1 to line 100 according to the sequence that you expecting if there are async function in between.
In your case, the JS indeed process all your ajax request in the correct sequence, it just it isn't wait for cart/deleteOneAjax to response then only process the next ajax cart/countAjax, and so on.
How to solve?
The old way: Promise Then
The new way: async await
Here i demonstrate with Promise Then way:
function deleteOne(id) {
return new Promise((resolve, reject) => {
$.post("/cart/deleteOneAjax/" + id, {}, function (data) {
if (!data) {
reject("no data")
} else {
resolve(data)
}
});
})
}
function count(id) {
return new Promise((resolve, reject) => {
$.post("/cart/countAjax/" + id, {}, function (data) {
if (!data) {
reject("no data")
} else {
resolve(data)
}
});
})
}
function productPrice(id) {
return new Promise((resolve, reject) => {
$.post("/cart/productPriceAjax/" + id, {}, function (data) {
if (!data) {
reject("no data")
} else {
resolve(data)
}
});
})
}
function totalPrice(id) {
return new Promise((resolve, reject) => {
$.post("/cart/totalPriceAjax/" + id, {}, function (data) {
if (!data) {
reject("no data")
} else {
resolve(data)
}
});
})
}
$(".minusCart").click(function () {
var id = $(this).attr("data-id");
deleteOne(id).then(deleted => {
// your logic before process next ajax
count(id).then(countData => {
// your logic before process next ajax
productPrice(id).then(productPriceData => {
// your logic before process next ajax
totalPrice(id).then(totalPriceData => {
})
})
})
}).catch(deleteError=>{
//logic for deleteOne rejection scenario
})
})
One down side of Promise Then is callback hell, but that is for your future study.
Using ajax instead of using this post method will make your request handling much more customizable.
As a solution of this problem you can use 'await' keyword before your post function call, then you have to make the click event function 'async'.
And easy solution for now is to make this calls synchronous, replace the 'post' methods with 'ajax' calls.
$.ajax({
type: 'POST',
url: url,
data: data,
success: success,
dataType: dataType,
async:false //Here I am making it synchronus
});
See the official documentation here: https://api.jquery.com/jquery.ajax/
I have an array of table names, so I need to fetch response of three tables. See the below code. Once the data is appended to the dom I need to call the successMessage method, now I am using setTimeout how can I use promise in the this scenario
let lists = ['table1', 'table2', 'table3']
lists.map(list => {
$.ajax({
url:`${rootUrl}/api/('${list}')`,
type: 'GET',
headers: {
accept: 'application/json'
},
success: res => dataDisplay(res),
error: err => console.log(JSON.stringify(err))
})
})
// displaying data
const dataDisplay = (res) => {
switch(res.TableName){
case 'Table1':
$("#tbl1 p").text(res.TableOriginalName)
$("#tbl1 .content p").text(res.TableDescription)
break;
case 'Table2':
$("#tbl2 p").text(res.TableOriginalName)
$("#tbl2 .content p").text(res.TableDescription)
break;
case 'Table3':
$("#tbl3 p").text(res.TableOriginalName)
$("#tbl3 .content p").text(res.TableDescription)
break;
default:
return
}
}
// successfully data appended
const successMessage = () => alert("data appended successfully")
// calling the success method once data is appended
setTimeout(successMessage, 3000)
You'd use Promise.all to wait for all of those requests to finish before showing the message. First, build up an array of the promises:
var promises = lists.map(list => $.ajax({
url:`${rootUrl}/api/('${list}')`,
type: 'GET',
headers: {
accept: 'application/json'
},
success: res => dataDisplay(res),
error: err => console.log(JSON.stringify(err))
}));
then wait for them to complete
Promise.all(promises).then(() => alert("data appended successfully"));
You can also use $.when for much the same purpose, but it's awkward to call:
$.when.apply($, promises).done(() => ...);
In comments you've said that dataDisplay loads a bunch of images and you need to delay the call to successMessage until after those images have loaded. To do that, you'll need to watch for the load event on the images. This can be a bit squirrelly because the images can load before you hook the event, so we'll want to use the image's complete flag as well. Something along these lines:
Promises.all(/*...*/).then(() => {
// Get all images in the tables we added to
let imgs = $("#tbl1 img, #tbl2 img, #tbl3 img");
// Hook up a function to check for completed images when we
// see a `load` event on any of them, and fire that proactively
imgs.on("load", checkComplete);
checkComplete();
function checkComplete() {
// Count any incomplete images; remove the handler from any
// complete image and remove it from our `imgs` set
let incomplete = 0;
imgs.get().forEach(img => {
if (img.complete || img.error) {
$(img).off("load", checkComplete);
imgs = imgs.not(img);
} else {
++incomplete;
}
});
if (incomplete == 0) {
// They're all done!
successMessage();
}
}
});
That's off the top of my head, may need some tweaking, but it should get you headed the right way.
You may try this code:
let tableNames = ['table1', 'table2', 'table3']
let promiseArr = tableNames.map((table) => {
return new Promise((resolve, reject) => {
$.ajax({
url:`${rootUrl}/api/('${list}')`,
type: 'GET',
headers: {
accept: 'application/json'
},
success: (res) => {
dataDisplay(res);
resolve(table);
},
error: (err) => {
console.log(JSON.stringify(err));
reject(table);
}
});
}).catch((e) => {
// if call to any of the table's url fails
// it will come here, var 'e' contains
// that table name, handle here
console.log(e + " has fails"); return e;
});
});
Promise.all(promiseArr).
then((result) => {
// this will run regardless of, if call to any table fails
console.log("success")
})
.catch((result) => {
console.log(result + " fails");
});
this will asynchronously call the tables, and at the end comes to Promise.all()'s then() even if call to some table fails
What I am trying to get done is extend JSON object in service and then pass it to controller.
JSON came to service from another service which makes backend call.
The code is pretty complicated so I add comments and console.logs:
//get games config object from another service
gamesConfig: gamesConfigService.gamesConfig(),
// prepare name of games icons. This is support function executed in next method
transformSpace: function(subject) {
var ensuredSubject = subject.toString().toLowerCase();
var transformedSubject = ensuredSubject.replace(/ /g, '_');
return transformedSubject;
},
//add iconname property to game config object
extendGameConfig: function() {
var that = this;
this.gamesConfig
.then(function (response) {
console.log(response.data); // this works and console.log my JSON
response.data.map(function(obj) {
return new Promise(function(res){
angular.extend(obj, {
iconname: that.transformSpace(obj.attributes.name) + "_icon.png"
});
});
});
}, function () {
console.log('errror');
});
This contains one support method transformSpace and main method which is not passing data correctly. ( I think )
I'm trying to receive this promise in controller by:
theService.getGamesObj.extendGameConfig()
.then(function (response) {
$scope.allGames = response;
console.log($scope.allGames);
}, function () {
console.log('err')
});
And then I'll use it in view. For now code above doesn't work and give me following error:
TypeError: Cannot read property 'then' of undefined
I've added comments where I think your code has gone wrong
extendGameConfig: function() {
// ***********
// use => functions, that = this wont be needed
var that = this;
// ***********
// if you want this this function to return something, add a return
// this is why you get the
// Cannot read property 'then' of undefined error
// as this function returns undefined
this.gamesConfig
.then(function (response) {
console.log(response.data); // this works and console.log my JSON
// ***********
// you're using .map ... and discarding the result!
response.data.map(function(obj) {
// ***********
// you're creating a promise that never resolves!
// also, why are you promisifying synchronous code?
return new Promise(function(res){
angular.extend(obj, {
iconname: that.transformSpace(obj.attributes.name) + "_icon.png"
});
});
});
}, function () {
console.log('errror');
});
so, try this
extendGameConfig: function() {
return this.gamesConfig
.then(response => {
return response.data.map(obj => {
return angular.extend(obj, {iconname: this.transformSpace(obj.attributes.name) + "_icon.png"});
});
}, function () {
console.log('errror');
});
or, better yet
extendGameConfig: function() {
return this.gamesConfig
.then(response =>
response.data.map(obj =>
angular.extend(obj, {iconname: this.transformSpace(obj.attributes.name) + "_icon.png"})
)
)
.catch(function (err) {
console.log('error', err);
throw err; // log the error, but you'll probably want to reject this promise so the calling code doesn't think there is success?
});
}
I have a scenario where I have to get the request payload passed when the service fails so I can return back along with error response. My code goes like below.
#Effect() doGetEvents$: Observable<Action> = this.actions$
.ofType(EVENTS)
.switchMap((action) => {
let eventDate = action.payload.date;
return this.http.service(action.payload);
})
.map(res => {
// success
if (res.status) {
return CustomActions.prototype.eventsResponse({ type: EVENTS_RESPONSE, payload: res.payload });
}
//failure
return CustomActions.prototype.EventsErrorResponse({
type: CustomActions.EVENTS_ERROR_RESPONSE,
payload: {
status: res.status,
errorMessage: res.errorMessage,
billDate: '10/01/2016', // <--- I need the eventDate got from the above switchMap
errorType: CustomActions.EVENTS + '_ERROR'
}
});
});
I tried passing like
.switchMap((action) => {
let eventDate = action.payload.date;
return [eventDate, this.http.service(action.payload)];
})
but this won't execute the http call and won't return the response on .map() args.
Also the are options to make the eventDate outside the scope of Effects and assign it when service fails but it is not a cleaner approach, there should be some way passing data round not sure what I missed!
If you want to include information from the payload, along with the HTTP service's result, you can use the map operator, like this:
.switchMap((action) => {
return this.http
.service(action.payload)
.map(result => [action.payload.date, result]);
})
.map(([date, result]) => { ... })