I have the following code:
var p1 = Q($.ajax({
url: "/api/test1"
}));
p1.then(function () {
console.log("success1");
});
p1.then(function () {
throw "some error";
console.log("success2");
});
p1.then(function () {
console.log("success3");
});
p1.catch(function () {
console.log("failure1");
});
p1.catch(function () {
console.log("failure2");
});
p1.finally(function () {
console.log("finally1");
});
p1.finally(function () {
console.log("finally2");
});
I am expecting the following output assuming that I get some data back from api/test1 - "success1, failure1, failure2, finally1, finally2"
What I actually get is
"success1, success3, finally1, finally3"
"Success3" is puzzling me - why is the code propagating to the 2nd then when an exception occurred?
How can I write this so that I can catch any exceptions that might occur in the then part(s)?
Ta
The reason "Success3" is showing is because your promises are not chained.
Basically the "then" calls (as well as the "catch" and "finally" for that matter) are interdependent of each other. If you think of it as a tree structure, they are all children of the p1 promise. If you want them to be dependent, they would be to have a child, grandchild, great-grandchild, etc relationship. If you want to chain them together, you can do something like:
var p1 = Q($.ajax({
url: "/api/test1"
}));
p1.then(function () {
console.log("success1");
}).then(function() {
throw "some error";
console.log("success2");
}).catch(function () {
console.log("failure");
});
Which is basically implicitly returning the promise after each function runs. The subsequent "then" (or catch/finally) is applied to that (implicit) return value.
Related
I want to return a zipcode before I call a 2nd service, so that -- from what I thought I know -- I can wrap in a promise, and then ask for the promise later. So I figured I would just place my 2nd service inside the promise of the 1st service. But chaining promises this way is not being friendly.
Angular factory is called up and inside the factory method:
var userGeoPromise = userService.getGeoposition().then(function (geoposition) {
vm.geoposition = geoposition;
return addressService.reverseGeocode(geoposition.coords);
}).then(function (data) {
vm.currentLocation = googleService.googleAddressComponentsToAddress(data.results[0]);
zipCodeCurrent = vm.currentLocation.zip;
});
Notice 2 things above:
I assigned the promise to var userGeoPromise
zipCodeCurrent is set which contains the zipcode
Promise Testing works fine:
userGeoPromise.then( function() {
console.log('should always show', zipCodeCurrent);
});
2nd service call:
userGeoPromise.then( function() {
var serviceBase = "http://localhost:2295/api/getservicezip/"+ zipCodeCurrent;
var serviceZipPromise = $http.get(serviceBase);
return serviceZipPromise.then(function (results) {
console.log('serviceZipPromise', results);
return results.data;
});
});
But now the site modal just spins when I put the serviceZipPromise.then... inside the other promise.
In a then callback you should return the result value, not assign it to zipCodeCurrent (or any other variable). You did this correctly in the first then callback, but you should apply the same principle in the second:
var userGeoPromise = userService.getGeoposition().then(function (geoposition) {
vm.geoposition = geoposition;
return addressService.reverseGeocode(geoposition.coords);
}).then(function (data) {
vm.currentLocation = googleService.googleAddressComponentsToAddress(data.results[0]);
return vm.currentLocation.zip; // *** return it
});
NB: I did not touch the assignments to the properties of vm, but normally you should avoid mutating variables which (apparently) exist outside of the scope of these callback functions.
The Promise test would look like this:
userGeoPromise.then( function(zipCodeCurrent) { // *** add the argument
console.log('should always show', zipCodeCurrent);
});
The 2nd service has a nested then call, which is something to avoid. Instead of calling a then on a nested, intermediate promise, return that promise, and apply the then on the main promise chain:
userGeoPromise.then( function(zipCodeCurrent) { // *** add the argument as in the test
var serviceBase = "http://localhost:2295/api/getservicezip/"+ zipCodeCurrent;
return $http.get(serviceBase); // *** return the promise
}).then( function (results) { // *** move the then-callback to the outer chain
console.log('serviceZipPromise', results);
return results.data;
}).catch( function (error) { // *** add error handling at the end of the chain
console.log('error occurred:', error);
});
Note how the nesting level is never more than 1.
Switch the order and add in 2 separate error handlers ( which is a good practice btw)
var serviceZipPromise = $http.get(serviceBase); // call up
return serviceZipPromise.then(function (results) {
console.log('should always show', zipCodeCurrent);
userGeoPromise.then(function () {
//console.log('serviceZipPromise', results);
console.log('inside servicezip ', zipCodeCurrent);
}, function (err) { // error from userGeoPromise
console.log(err);
});
return results.data; // THIS will return the data
}, function (err) { // outer error, this was switched
console.log(err);
});
This should not error, but for your real serviceBase to end up using the zip code, u might have to execute it a bit later
UPDATE answer for you
// created part of the api call
var xserviceBase = "http://localhost:2295"; // this looks to be your base
return userGeoPromise.then(function(){
return $http.get(xserviceBase + '/api/getserviceablezip/' + zipCodeCurrent).then(function(results){
return results.data;
})
});
Yes, I know that the 3 returns look a bit nasty, but it should work
it is a common pattern that we cascade across a list of sources of data with the first success breaking the chain like this:
var data = getData1();
if (!data) data = getData2();
if (!data) data = getData3();
et cetera. if the getDataN() functions are asynchronous, however, it leads us to 'callback hell':
var data;
getData1(function() {
getData2(function () {
getData3(function () { alert('not found'); })
})
});
where the implementations may look something like:
function getData1(callback) {
$.ajax({
url: '/my/url/1/',
success: function(ret) { data = ret },
error: callback
});
}
...with promises I would expect to write something like this:
$.when(getData1())
.then(function (x) { data = x; })
.fail(function () { return getData2(); })
.then(function (x) { data = x; })
.fail(function () { return getData3(); })
.then(function (x) { data = x; });
where the second .then actually refers to the return value of the first .fail, which is itself a promise, and which I understood was chained in as the input to the succeeding chain step.
clearly I'm wrong but what is the correct way to write this?
In most promise libs, you could chain .fail() or .catch() as in #mido22's answer, but jQuery's .fail() doesn't "handle" an error as such. It is guaranteed always to pass on the input promise (with unaltered state), which would not allow the required "break" of the cascade if/when success happens.
The only jQuery Promise method that can return a promise with a different state (or different value/reason) is .then().
Therefore you could write a chain which continues on error by specifying the next step as a then's error handler at each stage.
function getDataUntilAsyncSuccess() {
return $.Deferred().reject()
.then(null, getData1)
.then(null, getData2)
.then(null, getData3);
}
//The nulls ensure that success at any stage will pass straight through to the first non-null success handler.
getDataUntilAsyncSuccess().then(function (x) {
//"success" data is available here as `x`
}, function (err) {
console.log('not found');
});
But in practice, you might more typically create an array of functions or data objects which are invoked in turn with the help of Array method .reduce().
For example :
var fns = [
getData1,
getData2,
getData3,
getData4,
getData5
];
function getDataUntilAsyncSuccess(data) {
return data.reduce(function(promise, fn) {
return promise.then(null, fn);
}, $.Deferred().reject());// a rejected promise to get the chain started
}
getDataUntilAsyncSuccess(fns).then(function (x) {
//"success" data is available here as `x`
}, function (err) {
console.log('not found');
});
Or, as is probably a better solution here :
var urls = [
'/path/1/',
'/path/2/',
'/path/3/',
'/path/4/',
'/path/5/'
];
function getDataUntilAsyncSuccess(data) {
return data.reduce(function(promise, url) {
return promise.then(null, function() {
return getData(url);// call a generalised `getData()` function that accepts a URL.
});
}, $.Deferred().reject());// a rejected promise to get the chain started
}
getDataUntilAsyncSuccess(urls).then(function (x) {
//"success" data is available here as `x`
}, function (err) {
console.log('not found');
});
As a beginner, stumbling across the same problem, I just realized how much simpler this has become with async and await:
The synchronous pattern
var data = getData1();
if (!data) data = getData2();
if (!data) data = getData3();
can now easily be applied to asynchronous code:
let data = await getData1();
if (!data) data = await getData2();
if (!data) data = await getData3();
Just remember to add an async to the function that this code is used in.
I'm using Node.js and Bluebird to create some fairly complicated logic involving uncompressing a structured file, parsing JSON, creating and making changes to several MongoDB documents, and writing related files in multiple locations. I also have fairly complicated error handling for all of this depending on the state of the system when an error occurs.
I am having difficulty thinking of a good way to manage dependencies through the flow of promises.
My existing code basically looks like this:
var doStuff = function () {
var dependency1 = null;
var dependency2 = null;
promise1()
.then(function (value) {
dependency1 = value;
return promise2()
.then(function (value) {
dependency2 = value;
return promise3(dependency1)
.then(successFunction);
});
})
.catch(function (err) {
cleanupDependingOnSystemState(err, dependency1, dependency2);
});
};
Note that dependency1 isn't needed until promise3, and that the error handler needs to know about the dependencies.
To me this seems like spaghetti code (and my actual code is far worse with a lot of parallel control flow). I've also read that returning another promise inside of a .then callback is an antipattern. Is there a better/cleaner way of accomplishing what I'm trying to do?
I find both answers currently provided nice but clumsy. They're both good but contain overhead I don't think you need to have. If you instead use promises as proxies you get a lot of things for free.
var doStuff = function () {
var p1 = promise1();
var p2 = p1.then(promise2);
var p3 = p1.then(promise3); // if you actually need to wait for p2 here, do.
return Promise.all([p1, p2, p3]).catch(function(err){
// clean up based on err and state, can unwrap promises here
});
};
Please do not use successFunction and such it is an anti-pattern and loses information.
If you feel like you have to use successFunction you can write:
var doStuff = function () {
var p1 = promise1();
var p2 = p1.then(promise2);
var p3 = p1.then(promise3); // if you actually need to wait for p2 here, do.
Promise.join(p1, p2, p3, successFunction).catch(function(err){
// clean up based on err and state, can unwrap promises here
});
};
However, it is infinitely worse since it won't let the consumer handle errors they may be able to handle.
This question might be more appropriate for code review but here is how I'd approach it given this example:
var doStuff = function () {
// Set up your promises based on their dependencies. In your example
// promise2 does not use dependency1 so I left them unrelated.
var dep1Promise = promise1();
var dep2Promise = promise2();
var dep3Promise = dependency1Promise.then(function(value){
return promise3(value);
});
// Wait for all the promises the either succeed or error.
allResolved([dep1Promise, dep2Promise, dep3Promise])
.spread(function(dep1, dep2, dep3){
var err = dep1.error || dep2.error || dep3.error;
if (err){
// If any errored, call the function you prescribed
cleanupDependingOnSystemState(err, dep1.value, dep2.value);
} else {
// Call the success handler.
successFunction(dep3.value);
}
};
// Promise.all by default just fails on the first error, but since
// you want to pass any partial results to cleanupDependingOnSystemState,
// I added this helper.
function allResolved(promises){
return Promise.all(promises.map(function(promise){
return promise.then(function(value){
return {value: value};
}, function(err){
return {error: err};
});
});
}
The use of allResolved is only because of your callback specifics, if you had a more general error handler, you could simply resolve using Promise.all directly, or even:
var doStuff = function () {
// Set up your promises based on their dependencies. In your example
// promise2 does not use dependency1 so I left them unrelated.
var dep1Promise = promise1();
var dep2Promise = promise2();
var dep3Promise = dependency1Promise.then(function(value){
return promise3(value);
});
dep3Promise.then(successFunction, cleanupDependingOnSystemState);
};
It is certainly not an antipattern to return promises within thens, flattening nested promises is a feature of the promise spec.
Here's a possible rewrite, though I'm not sure it's cleaner:
var doStuff = function () {
promise1()
.then(function (value1) {
return promise2()
.then(function (value2) {
return promise3(value1)
.then(successFunction)
.finally(function() {
cleanup(null, value1, value2);
});
})
.finally(function() {
cleanup(null, value1, null);
});
})
.finally(function () {
cleanup(null, null, null);
});
};
Or another option, with atomic cleanup functions:
var doStuff = function () {
promise1()
.then(function (value1) {
return promise2()
.then(function (value2) {
return promise3(value1)
.then(successFunction)
.finally(function() {
cleanup3(value2);
});
})
.finally(function() {
cleanup2(value1);
});
})
.finally(function (err) {
cleanup1(err);
});
};
Really, I feel like there's not much you can do to clean this up. Event with vanilla try/catches, the best possible pattern is pretty similar to these.
I'm starting to use Jasmine for unit-testing of a JavaScript library that relies heavily on promises. I need to fail a test case asynchronously, and would like to write something like the following:
describe("An async test suite", function () {
it("should fail asynchronously", function (done, fail) {
var promise = myLibraryCall();
promise.then(done, function(reason) { fail(reason); });
});
});
However, there is nothing like a fail call available from what I can see. And I can't throw an exception in the asynchronous error case because it's not caught by Jasmine - all I get is an eventual generic timeout. What is the best way to address this?
Short of modifying Jasmine itself, the simple solution is to create a wrapper around a combination of expect and a custom matcher to fail with a given message.
function endTestAfter(promise, done) {
var customMatchers = {
toFailWith: function () {
return {
compare: function (actual, expected) {
return {
pass: false,
message: "Asynchronous test failure: " + JSON.stringify(expected)
};
}
}
}
};
jasmine.addMatchers(customMatchers);
promise.done(done, function (reason) {
expect(null).toFailWith(reason);
done();
});
}
This yields the following test suite code:
describe("An async test suite", function () {
it("should fail asynchronously", function (done, fail) {
var promise = myLibraryCall();
endTestAfter(promise, done);
});
});
I am having trouble testing the returned value of a function that waits for a promise to be resolved before executing.
Javascript Method (serviceClient._getProduct returns a jQuery ajax promise object)
serviceClient.getProductName = function(id, storeId) {
$.when(self._getProduct(id)).done(function(data) {
return data.name;
});
};
Test Code
before(function() {
serviceClient.sampleResponse = fixture.load('product_response.json')[0];
$.ajaxStub = sinon.stub($, 'ajax').returns(new $.Deferred().resolve(serviceClient.sampleResponse));
});
describe('.getProductName', function() {
it('should return the product name', function() {
var name = serviceClient.getProductName(serviceClient.sampleResponse.id);
$.when($.ajaxStub.returnValue).done(function() {
expect(name).to.equal(serviceClient.sampleResponse.name);
});
});
});
When I step through the call stack it seems to be executing correctly (steps inside of the promise callback in the actual js file, then steps into the test promise callback to assert), however the name variable in the test is still coming back as undefined. Any feedback would be appreciated.
You're trying to return data.name synchronously in serviceClient.getProductName, which can't be done since it depends on an asynchronous ajax request.
You're doing return data.name; inside your callback, which won't get you the expected result: if you'd return something synchronously, that return sentence should be at the scope outside that closure.
To simplify: if there's anything you can return there, it's a Deferred or Promise. It should be something like this:
serviceClient.getProductName = function(id, storeId) {
var deferredName = $.Deferred();
$.when(self._getProduct(id)).done(function(data) {
deferredName.resolve(data.name);
}).fail(function(jqXHR, textStatus, error) {
deferredName.reject(error);
});
return deferredName.promise();
// Or, if needed (I don't think so, it's resolved and rejected here)
// return deferredName;
};
Then, in your test:
before(function() {
serviceClient.sampleResponse = fixture.load('product_response.json')[0];
$.ajaxStub = sinon.stub($, 'ajax').returns(new $.Deferred().resolve(serviceClient.sampleResponse));
});
describe('.getProductName', function() {
it('should return the product name', function() {
serviceClient.getProductName(serviceClient.sampleResponse.id)
.done(function(name) {
expect(name).to.equal(serviceClient.sampleResponse.name);
});
});
});
Leaving code aside, the conceptual mistake is that you can return name synchronously from getProductName, when that function gets the product asyncrhonously (it won't be able to access its name until the deferred is resolved).
Note: you could implement getProductName using then, that returns a Promise (which is a subset of Deferred, but you can usually get away with it and code looks even clearer):
serviceClient.getProductName = function(id, storeId) {
return $.when(self._getProduct(id)).then(function(data) {
return data.name;
});
};
To remove that, also unnecessary $.when(...), you could return a Promise from _getProduct as well (if you've got a deferred, getting a Promise for it is as simple as calling deferred.promise()).