Handle ajax errors and execute the promise chain - javascript

I'm trying to provide a fallback for the failed ajax requests.
I want a global solution so I won't have to change every call in the code.
I tried providing an error handler to ajaxSetup, but the problem is I couldn't execute the chained callbacks.
$.ajaxSetup({
error: function() {
console.error('Error occurred')
return $.getJSON('https://jsonplaceholder.typicode.com/todos/1')
}
})
$.getJSON('https://jsonplaceholder.typicode.com/todos/0') // Id doesn't exist
.then(todo => console.log(todo))
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
Expected output
Error occurred
{
"id": 1,
"title": "...",
...
}

From jQuery 3.0 the callback method accepted are: done, always, fail.
So, i would have called the callback fail and in the inner, i resend the ajax call wrapped into a properly function with dynamic id
const submit = (id) => {
const xhr = $.getJSON(`https://jsonplaceholder.typicode.com/todos/${id}`)
.done(todo => console.log(todo))
.fail(err => { console.error(err); submit(id++); });
};
submit(0);

Related

Electron js win.webContents.print callback function doesn't work?

I am working on an application with Electron js and Vue js. I need to print the Synchronous request sent by Renderer with the print function. According to the result, I have to transmit the result to the renderer over the backend. Therefore, I use the callback function of the print function. But when I use this function, the print method does not work. I shared the codes below. Could there be an error?
ipcMain.on("set-print", function(event, arg) {
let options = {
silent: true,
deviceName: arg,
};
win.webContents.print(options, function(success) {
event.returnValue = success;
});
});
Try Promise function
win.webContents.print(options)
.then((success)=>{
console.log(success);
})
.catch((err)=>{
console.log(err)
});
https://www.electronjs.org/docs/api/webview-tag#webviewprintoptions

Jasmine spyOn reporting functions have been called when they haven't

I have the following code:
$scope.deleteJob = function(job) {
SandboxService.deleteJob(job.id).then(res => {
if (res.status == 200) {
ngToast.success();
$scope.refreshApps();
}
else {
ngToast.danger();
}
});
};
And the following unit test:
it('should show success toast on delete and refresh apps', () => {
spyOn(sandboxService, 'deleteJob').and.returnValue(Promise.resolve({status: 500}));
spyOn(ngToast, 'success');
spyOn(scope, 'refreshApps');
let mockJob = {
'id': 1
};
scope.deleteJob(mockJob);
sandboxService.deleteJob().then(() => {
expect(ngToast.success).toHaveBeenCalled();
expect(scope.refreshApps).toHaveBeenCalled();
});
});
Basically when deleting a job, if the delete was a success with a returned status of 200 then show a success toast and refresh, else show a danger toast.
I expect the test to fail, as it returns a status of 500, but it passes. This implies that ngToast.success() and scope.refreshApps() have been called.
I added some logs to the code, and it does return status: 500 and go to the else block.
What am I missing here?
The problem is related to the asynchronous nature of deleteJob. Your it test ends even before expect is performed. Therefore you need some sort of synchronization. This could basically be done with fakeAsync and tick from #angular/core/testing.
it('should show success toast on delete and refresh apps', fakeAsync(() => {
...
sandboxService.deleteJob();
tick();
expect(ngToast.success).toHaveBeenCalled();
expect(scope.refreshApps).toHaveBeenCalled();
}));
The problem however is that you're overwriting the original behaviour of deleteJob with the spy below, hence ngToast.success and scope.refreshApps won't be called and the test will fail.
spyOn(sandboxService, 'deleteJob').and.returnValue(Promise.resolve({status: 500}));
#uminder's answer pointed out that the test was finishing before the expect functions were even called due the asynchronous nature - verified by adding some logs inside the test.
The solution was to add an argument to the test that is to be called when the test has finished: https://jasmine.github.io/2.0/introduction.html#section-Asynchronous_Support
it('should show success toast on delete and refresh apps', (done) => {
spyOn(sandboxService, 'deleteJob').and.returnValue(Promise.resolve({status: 200}));
spyOn(ngToast, 'success');
spyOn(scope, 'refreshApps');
let mockJob = {
'id': 1
};
scope.deleteJob(mockJob);
sandboxService.deleteJob().then(() => {
expect(ngToast.success).toHaveBeenCalled();
expect(scope.refreshApps).toHaveBeenCalled();
done();
});
});

Is it possible/can you loop ajax GET request each time?

I've looked everywhere, and I am not quite getting if it's possible or how to loop through an ajax request, cycling through the values in an array.
So it would need to make an ajax request one of the data values (serial) as array[0], finishing the request, then does the next request with array[1], and so on.
My code:
$.ajax({
url: 'example.com',
type: 'GET',
data: {
message: message,
user: user,
serial: i
},
success: function(response) {
alert("Sent");
},
error: function(XMLHttpRequest, textStatus, errorThrown) {
alert("Fail");
}
});
So this will work for one defined serial, but how would it work when serial (the variable 'i') is an array, holding many serials?
Also it shouldn't send the array, it needs to cycle through, sending one value at a time.
Any help is most appreciated at this point.
Create a recursive function that does an ajax call. When the ajax call ends, the function calls itself (recursion) and passes in an updated index value to use on passed in array for the next ajax call.
/**
* Recursive function that make an ajax call over the {index} element inside an {array}
* #param {Array} array the array to loop through
* #param {Number} index current index
*/
function Caller(array, index){
if(array.length <= index){return;}
$.ajax({
url: 'example.com',
type: 'GET',
data: {
message: message,
user: user,
serial: array[index]
},
success: function(response) {
alert("Sent");
},
error: function(XMLHttpRequest, textStatus, errorThrown) {
alert("Fail");
},
complete: function(){
Caller(array,++index);
}
}
The recursive function calls itself on the complete callback (which is triggered after the call completes whether is was a success or error).
By doing it this way you get through the array and only send an ajax request when the previous request is finished.
Try to use forEach():
array.forEach(element => {
$.ajax({
...
data {.., serial: element...}
...
});
});
It's 2018 so there are multiple, nice ways of doing this;
You can use Promises, $.ajax actually returns one; and async/await to perform XHR requests serially.
You can keep your callback-style code and use a small utility function to abstract the async iteration in a nice readable way that you can reuse over and over.
I'll cover both cases.
Async Iteration with Promises and async/await
Since jQuery 1.5, $.ajax returns a Promise. So if you're using a modern browser you can just await it.
This is by far most elegant and terse way since the code looks like synchronous code hence it's far more readable. Be aware that while the code looks synchronous, it's in fact non-blocking.
const getPosts = async (pages) => {
const posts = []
for (const page of pages) {
const post = await $.ajax({
url: 'https://jsonplaceholder.typicode.com/posts/' + page
})
posts.push(post)
}
return posts
}
getPosts([1, 2, 3, 4, 5]).then(posts => {
console.log(posts)
}).catch(err => {
console.error(err)
})
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
Async Iteration with callbacks
This is the "traditional" way of doing asynchronous operations, that uses callbacks. This style doesn't require a modern browser at all since at it's base level it just passes around functions to achieve non-blocking behaviour.
However, this type of code is far harder to work with; You need to rely on utility functions that wrap common operations (looping, mapping etc) to effectively work with such code.
I've written a small utility function that let's you:
Iterate over the elements of an Array.
Specify a callback function that get's called for each iteration
This function get's called on each iteration. The currently-iterated over Array element is passed to it, together with a next argument that you need to call in order to proceed to the next iteration.
Calling the next of this callback function pushes the result into a final result Array, and proceeds to the next iteration.
Specify a final callback function that get's called when all the iterations have finished.
If I'm not mistaken, this is identical in operation to the async.mapSeries method of the popular async module.
async.mapSeries:
In the following example, I'm passing an Array of posts to fetch from a REST API.
When all the iterations are complete, the posts argument in the final callback contains an Array with 5 posts.
It takes advantage of error-first callbacks, a common pattern to gracefully propagate errors up the callback chain if something goes awry in your async operations.
// async.mapSeries utility Function
const async = {
mapSeries: function(arr, onIteration, onDone, { i = 0, acc = [] } = {}) {
arr.length ?
onIteration(arr[i], (err, result) => {
if (err) return onDone(err)
acc.push(result)
acc.length < arr.length ?
this.mapSeries(arr, onIteration, onDone, {
i: ++i, acc
}) : onDone(null, acc)
})
: onDone(null, arr)
}
}
// Usage
async.mapSeries([1, 2, 3, 4, 5], (page, next) => {
$.ajax({
url: 'https://jsonplaceholder.typicode.com/posts/' + page,
success: response => {
next(null, response)
},
error: (XMLHttpRequest, textStatus, err) => {
next(err)
}
})
}, (err, posts) => {
if (err) return console.error('Error:', err)
console.log(posts)
})
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>

using bluebird with undefined success callback function

I am using bluebird library over memcached.
memcached.set('foo', 'bar', 10, function (err) { /* stuff */ });
this function does not call success callback in second parameter so seems like .then(res) function does not getting called.
Promise.promisifyAll(memcached);
memcached.setAsync(hashedCacheKey, obj).then(function (res) {
resolve(res);
}).catch(function (err) {
reject(err, null);
});
is there any way for me to handle uncalled success event?
The primary issue here is that you're not providing a timeout argument to memcached.setAsync, but it's a mandatory argument for memcached.set. These two lines are equivalent:
memcached.set("foo", "bar", () => { /* this is never called */ });
memcached.setAsync("foo", "bar").then(() => { /* this is never called, either */ })
Add a timeout argument and your code should work as expected.

How can I analyse my JS program to ensure a particular method is always called with 2 arguments?

We're using promises in an AngularJS project and want to ensure that the then method is always called with 2 arguments, the 2nd being an error handler, like so:
$http.get(url).then(function () {
console.log('hooray!');
}, function (error) {
console.log('boo! error');
});
We're using jshint on the project. Can that perform this analysis?
Some calls to then do not require an error handler, i.e. in a chain of handlers:
$http.get(url1).then(function () {
console.log('hooray!');
return $http.get(url2);
}).then(function () {
console.log('hooray again! all our data loaded');
}, function (error) {
console.log('boo! error in one of our 2 requests');
});
We could mark these up using jshint's /* jshint ignore:start */ comments or similar.

Categories