It doesn't seem like I can return a deferred object if it failed. I have a pretty nested ajax request that goes into a queue so I need a way to return the request as a deferred object. Everything is great if the request is successful, but I am stuck on getting failures to propagate.
https://jsfiddle.net/3wtady9r/
function doAjax(file) {
var defer = $.Deferred();
var fakeFile = file;
var data = null;
ajax();
function ajax() {
return $.get(fakeFile)
.done(function(data) {
data = data;
defer.resolve(data);
})
.fail(function() {
defer.fail();
})
}
return defer.promise(data);
}
var thisWillWork = '';
doAjax(thisWillWork)
.done(function() {
console.log('done')
})
.fail(function() {
console.error('fail')
})
.always(function() {
console.log('always')
})
var thisWontWork = 'fakeFile.html'
doAjax(thisWontWork)
.done(function() {
console.log('done')
})
.fail(function() {
console.error('fail')
})
.always(function() {
console.log('always')
})
When I do doAjax(thisWontWork) I am expecting to get the console error and also .always, but neither fire.
There are a bunch of different issues there.
fail is a method for hooking up handlers to get called if the promise is rejected. To reject it, use reject.
You don't want to pass data into defer.promise at the end.
You don't want or need a data variable at all, just the argument you receive in success.
Since you never use the value you return from your own ajax function, there's no need to return it.
jQuery's ajax already returns a promise that does what your promise does, so your promise can just be removed entirely. This happens often enough there's an antipattern named for it. Every time you think you need to create a promise, stop yourself and think: Do I already have one? Sure, sometimes the answer is no, but often it's yes. :-)
As of point #5, we end up with:
function doAjax(file) {
var fakeFile = file;
return $.get(fakeFile);
}
...which means, of course, that you don't need doAjax at all. Just call $.get.
But if for some reason you haven't shared you need your own promise, then just points 1-4:
function doAjax(file) {
var defer = $.Deferred();
var fakeFile = file;
ajax();
function ajax() {
$.get(fakeFile)
.done(function(data) {
defer.resolve(data);
})
.fail(function() {
defer.reject();
});
}
return defer.promise();
}
It should be defer.reject(); not defer.fail();
Also you can add data in the reject like you did in defer.resolve().
Related
I have 2 csv's that I'm trying to read in, after which I use the data in those two to do stuff:
function getData() {
var deferredObject = $.Deferred(); //representation of some asynchronous work
d3.csv("./parse_shp.csv", function(data) {
console.log(data);
shp_array = data;
});
d3.csv("./fao_coutnry_shp.csv", function(data) {
console.log(data);
fao_array = data;
});
//once both of those are done, resolve the promise
deferredObject.resolve();
return deferredObject.promise();
}
function LevenshteinDistance() {
console.log("do stuff with the data");
}
//call LevenDistance after the promise has been resolved
getData().then(LevensteinDistance());
But that's not working... it'll print the line "do something with the data" before printing the data of the csv's.
What am I doing wrong? I used this link as an example.
I don't understand how connect deferredObject and getData()? Because even if I create the deferred object in the function, won't it just asynchronously do the csv reads anyway and then erroneously call defferedObject.resolve()?
Anyway, I'm new to promises so any help would be greatly appreciated!!
The argument to .then() must be a function. You're calling the function immediately, because you have () after the function name. It should be:
getData().then(LevenshteinDistance);
I want to use the result of first api, into second api call. Scenario is like that, I want to use the result of first api, into second api call. If I am correct then I want synchronous api call(not sure). I tried to write following function but not working. function2 is call before function1. In function2 we are use result1 which is only come when function1 is called before function2, How I do.
$scope.function1 = function(){
var deferred= $q.defer();
$http.post(api1, data1)
.success(function(response, status) {
if (response.error == 0) {
console.log(response.result);
$scope.result1=response.result;
}
}) ;
deferred.resolve('Success') ;
return deferred.promise;
};
var promise = $scope.addDefaultValue();
promise.then(function(){
$scope.function2();
});
$scope.function2=function(){
var deferred = $q.defer();
$http.post(api2,result1)
.success(function(response, status){
if(response.error == 0){
}
});
deferred.resolve('Success') ;
return deferred.promise;
}
You could follow promise chain pattern here, follow chaining using .then on promise object.
No need to create extra overhead promise using $q, as $http methods returns promise object when they start an ajax.
Code
$scope.function1 = function() {
return $http.post(api1, data1)
.then(function(d) {
var response = d.data;
if (response.error == 0) {
console.log(response.result);
$scope.result1 = response.result;
}
return response;
});
};
$scope.function1().then(function(data) {
$scope.function2();
}, function(error) {
});
You cannot convert $http requests to "synchronous". That's not what "deferred" does. Deferred is a way to convert non-promise-capable functions to promise-capable functions. $http functions return promise objects so you don't need to use deferred.
$http.post(api, data1).then(function (response) {
$scope.result1 = response.data.result;
// return is important here
// so that you can keep chaining with .then
return $http.post(api2, response.data.result);
}).then(function (response) {
// here you have response from api2
$scope.result2 = response.data.result;
console.log(response.data);
}).catch(function (error) {
// here you can handle errors
// from either api calls
// second api call won't be made if the first one fails
console.log(error);
});
I want to be able to chain promises in order to make code synchronous. My problem is that depending on result of first $http request I could either be wanting to send another or not.
In case if I choose not to send another $http request I don't need my second then() to do anything. But since my second then() doesn't know about all this and it's hanging there anyway so I figured I need to return from first then some fake dummy promise. But I would also like to recognize this case in second then() . I came up with returning $q.when('some value') from first case. Here is the code:
$http.get(url, params)
.then(function(res) {
res = res.data;
if (res.status == 'ok' && res.rows.length > 0) {
$scope.roomTypes = res.rows;
return $q.when({ isDummy: true }); //in this case I don't need to send another request
} else if (res.rows.length === 0 && $scope.request.roomType) {
return $http.get(url2, params2); //make second request and return then`able promise
}
}, function(res) {
throw 'Error';
})
.then(function(res) {
res = res.data;
if (res.status == 'ok') {
var roomType = {
room_type: res.roomType.id,
description: res.roomType.description
};
$scope.roomTypes.push(roomType);
} else if (res.isDummy) {
//ok that's our dummy promise
} else {
//format of response unexpected it means something went wrong
throw "error";
}
}, funcrtion(res) {
throw "some Error";
})
.catch(function(res) {...})
.finally(function() {...});
The thing is I want to see value with which promise was resolved ({isDummy: true}), but how do I do that? I get undefined in my res parameter.
res will be undefined here
.then(function(res) {
res = res.data;
...
because there's no data property on {isDummy: true} object.
I think the basic issue here is confusing promises with promise handlers. The success handlers should return a value, not another promise. When you are in a promise success handler you are 'wrapped' by the promise mechanism which takes you returned value and passes it on to the next handler. This means your second promise does not see the initial response but what the first promise returned.
The value is processed as it goes along unless you pass it as is.
For example
This all comes to that your line
return $q.when({isDummy: true});
Should be
return {isDummy: true};
The problem is the other case, where you want to continue to a next query. I would probably do one of the following:
1. Start in the first promise the handling (with the related logic from the first handler).
2. Pass on url2 and params - return({url: url2, params: params}) and handle them in the second promise.
Note the promise chanins can even break in the middle, if any of the handlers rejects, following success handlers will not be called, here is a simple example (make sure you open your devtools console to see the log).
Try to return any object except promise or deferred :) And its value will be passed to then. Like this:
return { isDummy: true };
Example code: https://jsfiddle.net/817pwvus/
From jQuery when documentation:
If a single argument is passed to jQuery.when() and it is not a Deferred or a Promise, it will be treated as a resolved Deferred and any doneCallbacks attached will be executed immediately. The doneCallbacks are passed the original argument.
If you want code sync, you can always write a long code block in the first then.
if you want to chain promise (Post:url1)->(Post:url2) and so on:
1.The return is useless.
2. Let's assume you have 2 $http promises you want to chain, both from a service called $users, for example, and they called GetUserAge and GetRelevantBars and the second query is based on the first one's results.
angular
.module("moduleA")
.controller("exmapleCtrl",exampleCtrl);
exampleCtrl.$injector=["$users"];
function exampleCtrl($users){
var vm=this;
vm.query = $users.GetUserAge().then( /*OnSuccess*/ GetUserAgeCB)
//Callback of the GetUserAgeCB;
function GetUserAgeCB(result){
$users.GetRelevantBars().then( /*OnSuccess*/ GetRelevantBarsCB);
/*continuation of CallBack code*/
}
//Callback of the GetRelevantBarsCB;
function GetRelevantBarsCB(result){
/*CallBack code*/
}
}
hope this is understandable..
Instead of returning a dummy value and ignoring that explictly, you rather should nest and attach the second handler only to the promise that needs it:
$http.get(url, params)
.then(function(res) {
var data = res.data;
if (data.status == 'ok' && data.rows.length > 0) {
$scope.roomTypes = data.rows;
return; // do nothing
} else if (res.rows.length === 0 && $scope.request.roomType) {
return $http.get(url2, params2)
.then(function(res) {
var data = res.data;
if (data.status == 'ok')
$scope.roomTypes.push({
room_type: data.roomType.id,
description: data.roomType.description
});
else
throw "error"; //format of response unexpected it means something went wrong
});
}
})
.catch(function(res) {…})
.finally(function() {…});
I have a function which issues two async requests before yielding some data.
The caller of the method does not need to know about its implementation details. All the caller needs is:
The data returned from the second request.
The ability to call abort and not be returned data.
This is complicated by the fact that abort can be called after the first promise is done. The second request is already in-flight, but the caller has yet to receive data. So, the caller assumes it can call abort, but rejecting the first promise will have no effect.
I work around this issue with the following, but it feels pretty hacky. Am I missing something?
var ajaxOptions = {
url: 'https://www.googleapis.com/youtube/v3/search',
data: {
part: 'id',
key: 'AIzaSyDBCJuq0aey3bL3K6C0l4mKzT_y8zy9Msw',
q: 'Hello'
}
};
function search(options) {
var jqXHR = $.ajax(ajaxOptions);
var innerJqXHR = null;
jqXHR.then(function(data, statusText, jqXHR) {
innerJqXHR = $.ajax(ajaxOptions);
innerJqXHR.done(options.done);
innerJqXHR.fail(options.fail);
return innerJqXHR;
}, options.fail);
return {
promise: jqXHR,
innerPromise: innerJqXHR,
fullAbort: function() {
jqXHR.abort();
if (innerJqXHR !== null) {
innerJqXHR.abort();
}
}
}
}
var searchReturn = search({
done: function() {
console.log('inner done');
},
fail: function() {
console.log('inner or outer fail');
}
});
searchReturn.fullAbort();
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
Since your code looks a little like pseudo-code (unanswered questions in it for me), the best I can offer is a framework of an idea. The general idea is that you return two things from your search function, a promise that is resolved only when both ajax calls are resolved and an abort function that can be called to abort the process.
function search(options) {
var activeAjax = $.ajax(args for first ajax call).then(function(data) {
// second ajax call changes activeAjax so abort will work
// on the active ajax call
activeAjax = $.ajax(args for second ajax call).then(function(data) {
activeAjax = null;
// collect your final data here and return it
// this will be the resolved value of the final promise
return finalData;
});
return activeAjax;
});
return {
promise: activeAjax,
abort: function() {
if (activeAjax) {
activeAjax.abort();
}
}
};
}
// usage:
var searchInProgress = search(...);
searchInProgress.promise.then(function(data) {
// search finished successfully, answer is in data
}, function(err) {
// either one of the promises failed
});
// and, at any time, you can call searchInProgress.abort();
when a function returns a promise, I can call some other function after the first one did it's work:
do_stuff().then(function(){
alert('yoooo');
});
and do_stuff() looks like this:
function do_stuff(){
if(!got_the_data){
var d = $.Deferred();
$.ajax({
success: function(html){
$('#box').append(html);
$('#box').addClass('visible');
$('#box').on('transitionend webkitTransitionEnd', function(){
got_the_data = true;
d.resolve();
});
}
});
return d.promise();
}else{
// got the data, what now?
}
}
but what do I return if I already did the ajax request (result is cached) and I don't have to wait for anything? I can't return d.resolve() because the function that was attached to then() won't fire :/
and I can't return d.promise because I have to resolve the "d" somewhere
You can choose between two approaches; caching data or caching promises.
Here's two examples, both of which key on url, though any other key may be used, as appropriate - as long as it uniquely identifies each individual case.
Cache data
var dataCache = {};
function do_stuff_1(url) {
if(dataCache[url] === undefined) {
return $.ajax({
url: url
}).then(function(data) {
dataCache[url] = data;
return data;
});
} else {
return $.when(dataCache[url]);
}
}
Cache promises
var promiseCache = {};
function do_stuff_2(url) {
if(!promiseCache[url]) {
promiseCache[url] = $.ajax({
url: url
});
}
return promiseCache[url];
}
In both approaches, the function will (barring an uncaught error) return a promise, either by executing $.ajax() or by retrieving data/promise from the cache.
In most applications, there's virtually nothing to distinguish one approach from the other.
In an application where the cache is likely to grow to be large, then cache the data and avoid the overhead of caching promise wrappers.
If necessary, the cache can be pre-loaded, thus avoiding the need to fetch known data :
var dataCache = {
'/path/to/data/a': 'A',
'/path/to/data/b': 'B'
}
or
var promiseCache = {
'/path/to/data/a': $.when('A'),
'/path/to/data/b': $.when('B')
}
The simplest solution is to just return an empty already-resolved promise in the else clause:
return $.Deferred().resolve();
To avoid the Promise anti-pattern your code might be better structured thus:
function show_stuff(html) {
return $.Deferred(function(def) {
$('#box').append(html);
$('#box').addClass('visible');
$('#box').on('transitionend webkitTransitionEnd', def.resolve);
});
}
function do_stuff() {
if (got_the_data) {
return $.Deferred().resolve();
} else {
return $.ajax(...).then(show_stuff);
}
}
Note that there's no line (yet) setting got_the_data = true - you should consider whether it's really appropriate to wait until the data has been displayed to set this flag, otherwise there's nothing to prevent multiple invocations of do_stuff all resulting in new stuff getting added to #box. IMHO you would be better with a getting_the_data flag.