I'm facing an troublesome issue, because it does not occurs every time:
I call webservices with angularJS (hosted in Django/Tastypie), with promises sothat WS2 (Web Service #2) is called only when WS1 has been successfully executed, etc.
The code looks like this:
myfunc = function() {
callPromiseWS1()
.then(function(data){
callPromiseWS2()
})
.then(function(data){
callPromiseWS3()
})
};
WS2 is a POST request that writes stuff in Database.
WS3 is a POST request which uses stuff created by WS2.
Sometimes (and not always), WS3 fails because the object that should have been already created by WS2 does NOT exist.
Of course, if I try to execute the WS3 request later manually, it works.
I have the feeling that WS2 and WS3 are executed in parallel, that's not what I expect.
Any idea? Thank you.
You're missing 3 return statements, but the behavior you're experiencing is because of one in particular:
myfunc = function() {
return callPromiseWS1()
.then(function(data){
return callPromiseWS2(); // this one
})
.then(function(data){
return callPromiseWS3();
})
};
If you don't return the promise that callPromiseWS2 returns, the then() won't know that it's dealing with a promise and just move onto the next then as soon as the synchronous code in callPromiseWS2 completes.
Note that if you're not actually using the data parameter in each step (or if you're just going to pass it right into callPromiseWS2 and callPromiseWS3 as the only parameter), you can simplify this and eliminate the need for two of the above return statements and a lot of the code:
myfunc = function() {
return callPromiseWS1()
.then(callPromiseWS2)
.then(callPromiseWS3);
};
Remember that any function that uses promises should either return the promise, or call .done() at the end.
As a matter of personal preference, I think it's a good idea to use a naming scheme for functions that return promises so that you know they return a promise that needs to be dealt with.
I'm not an expert on promises (and maybe the code you posted was simplified), but the first thing I would check is that the first then returns something, or the second .then will have the same object returned by callPromiseWS1() to work on.
I believe it should be something like:
callPromiseWS1()
.then(function(data){
return callPromiseWS2();
})
.then(function(data){
return callPromiseWS3();
})
But, as I said, I'm not an expert on promises and I don't know if it was just a simplified example of your structure.
Related
I initially assumed that passing a bare Promise.mapSeries(...) call as an argument to .then() would be the same as wrapping it in a function, like .then(function() { return Promise.mapSeries(...); }). Having written out this question, I'm no longer entirely sure why it works at all.
In the simplified code below, I open a couple of databases asynchronously (openDBAsync()) and then read a file containing a JS object. I use _.map() to iterate over all of the key/value pairs in the object and asynchronously update their values in a database, while keeping track of which ones meet certain criteria (whether the value is odd, in this toy example). Promise.all() waits for all of the async database calls to settle, and then Promise.mapSeries() is used to process each of the subset of keys, which makes another async database call for each one. Finally, I close all the databases.
function processData(path)
{
var oddKeys = [];
return openDBAsync()
.then(function() { return readFileAsync(path); })
.then(function(dataObject) {
return Promise.all(_.map(dataObject, function(value, key) {
if (value % 2) {
oddKeys.push(key);
}
return updateDBAsync(key, ++value);
}))
.then(Promise.mapSeries(
oddKeys,
function(key) {
return updateOddDBAsync(key);
}
))
})
.then(closeDBAsync);
}
The problem is that the database throws errors complaining that I'm trying to update the database after it's been closed. That means that some of the promises generated in the .mapSeries() call are being called after the final closeDBAsync(). I expected all of them to settle before the final .then() call.
If I wrap the call to Promise.mapSeries() in a function:
.then(function() {
return Promise.mapSeries(
oddKeys,
function(key) {
return updateOddDBAsync(key);
}
);
})
Then I don't get any errors. It also works if I put a .delay(2000) before the close database call, which indicates that Promise.mapSeries() isn't settling all of the promises before it finishes.
This seems like either a bug in Bluebird, or, much more likely, I'm not understanding something fundamental about how Promise.mapSeries() works. Any guidance would be much appreciated.
much more likely, I'm not understanding something fundamental about how Promise.mapSeries() works
Nope, this seems to be more a misunderstanding about how .then(…) works.
The then method of promises does always take a callback function (if you're passing anything else [but null], Bluebird should spit out a warning!). By calling .then(Promise.mapSeries(…)), you were passing a promise instead, which is simply ignored. Being ignored, it also is not awaited by anything, which leads to that error of the database being closed too early.
But the direct call to Promise.mapSeries() doesn't get applied to the array immediately. If it was, the array would be empty and this code wouldn't work at all.
Yes it does. Your array is filled from by the _.map callback, which is executed synchronously, before then and mapSeries are invoked.
So the solution is indeed to wrap the call in a function expression, which will only be executed when the Promise.all(…) fulfills, and whose result will then not be ignored but rather awaited. There might be more, different solutions, depending on what degree of parallel execution you want to allow.
Btw, given you are doing a database transaction, your current code is quite fragile. Look into the promise disposer pattern.
(my case applies to C#, MVC, returning JSON, got jquery, angular), but I expect it applies to more than that.
I have a website where my angular/html/js calls ~7 services through Angular controllers and async-gets/displays data (weather, road conditions, etc). Some of these take longer than others (from ms to ~10s). I'd like to have a single call to my service which returns all of this data - but doesn't wait until the last call to return anything (10s).
Is there a way to make a single call, and return results as I have them and they get displayed accordingly? Do I need to have a repeating call which has a boolean like "IsMore=T" and calls the service again? (doesn't sound efficient).
Ideally, I'd like to keep a response channel open and keeping pumping results until it's done. Possible?
I'm not sure I understand completely, but I think you could just chain the response promises together, something like:
$scope.getWeatherData = function () {
return myWeatherService.get().then(function (resp) {
$scope.weatherData = resp;
});
}
$scope.getTrafficData = function () {
return myTrafficService.get().then(function (resp) {
$scope.trafficData = resp;
});
}
$scope.getWeatherData.then(getTrafficData).then(...chain others...);
This assumes that the service calls return a $http promise.
Anyway, whenever the promise comes in, the data will be on $scope, and hence the display will be updating as the promises arrive. It sounds like you might be using something like $q.all(), which would wait until all promises are resolved.
Buildilng on #reptilicus' answer:
$scope.getWeatherData = function () {
return myWeatherService.get().then(function (resp) {
$scope.weatherData = resp;
});
};
$scope.getTrafficData = function () {
return myTrafficService.get().then(function (resp) {
$scope.trafficData = resp;
});
};
$q.all([
$scope.getWeatherData(),
$scope.getTrafficData()
]).then(function () {
// do whatever's next, or nothing
});
... would request/receive both responses in parallel (if that's what you want). The relevant $scope property for each request will be populated when the related response is received, and the "do whatever's next" code will run once they are all complete.
Note, you need to inject $q to your controller constructor for this to work. :)
Edit: I just noticed that #reptilicus did mention $q.all. The difference between chaining the .thens and $q.all is that under chaining, one request wouldn't start until the previous was received....
I'm doing some slightly complex post processing on some mongoose documents and I ended up adding Q to help with managing the async. I can get things working but I'm a little confused by what's going on under the hood and why I get different behavior if I use a function to return the promise returned by Q.nfcall rather than just using Q.nfcall which returns a promise itself. The scenario I'm describing is probably clearer in code:
...
return Q.all([
Q.nfcall(doc.save.bind(doc)),
Q.nfcall(doc2.save.bind(doc2))
])
// confused on this line
.then(Q.nfcall(doc3.save.bind(doc3)))
.then(function(val) {
console.log('val', val);
});
So this ends up logging an array which are the results of the Q.all promises. What I expect there is the results of the doc3 save since it comes later in the chain.
Now when I change the doc3 save to use an anonymous function like this:
...
return Q.all([
Q.nfcall(doc.save.bind(doc)),
Q.nfcall(doc2.save.bind(doc2))
])
// changed this line
.then(function() {
return Q.nfcall(doc3.save.bind(doc3))
})
.then(function(val) {
console.log('val', val);
});
I get the result I expect, namely that the val passed to the final then is the result from the doc3 save.
I feel like there is something big I'm missing. Isn't this code functionally equivalent?
Without an anonymous function in your then(), the promise won't pass along the resolved data without ignoring anything else that's there. It preserves the ability to pass that data through more resolutions. The anonymous function allows you to accept the data as an argument and then do something more with it before proceeding.
I try to primeData before the GUI binding (like in Papa, John's CodeCamperJumpStart), but I can't make it work.
Here is my code in datacontext.js:
function primeData() {
Q.all([getLookups()]);
};
function getLookups() {
var query = breeze.EntityQuery.from("Lookups");
return manager.executeQuery(query).then(succeeded);
function succeeded(data) {
return data.results[0];
}
}
However, Q.all() doesn't seem to put the call wait for succeeded to occur first. What's wrong?
The call to Q.all is happening, but nobody is waiting for its promise to return. So when you run primeData from somewhere, the code right after it (which you do not show) will just continue on.
But I think your question is more around the .then in the getLookups ... for that, since you are using Breeze, you need to look into the to$q method, which converts Q to $q (which is what Angular wants)
I'm writing an AngularJS service for a SignalR hub. Here's my factory for the service:
.factory('gameManager', [function () {
$.connection.hub.start();
var manager = $.connection.gameManager;
return manager;
}])
That code would be perfect, except that that .start() call is asynchronous, and the hub has not completed starting by the time the manager is returned. Basically, I want to block until the start is complete before returning the manager. The .start() method returns a Jquery deferred object, which I'm guessing is part of the answer, but I don't know how to use it without a callback function?
Something like the following should do the trick.
app.factory('gameManager', [function () {
return $.connection.hub.start().then(function() {
return $.connection.gameManager;
});
}])
Now your callback function will return a deferred/promise too, so the service consumer will need to be expecting that. Your consuming code might look something like this:
gameManager.then(function(gameManager) {
// do whatever with game manager
gameManager.doSomething();
});
The docs for jquery Deferred are here. In particular, check out Deferred.then().
Note that:
the deferred.then() method returns a new promise that can filter the status and values of a deferred through a function ... These filter functions can return a new value to be passed along to the promise's .done() or .fail() callbacks, or they can return another observable object (Deferred, Promise, etc) which will pass its resolved / rejected status and values to the promise's callbacks...
update:
An alternate approach (and probably the better approach - since it won't require that your consumer handle the promise) is to let the hub initialize completely, before setting up your factory, and kicking off your app controller. Something like this...
$.connection.hub.start().then(function() {
app.factory('gameManager', function() {
return $.connection.gameManager;
});
// ...
// kick off the rest of the app..
});
You will not find what you are looking for, you will have to go with Lee's answer. Javascript is mostly single-threaded and does not allow blocking (with specific exceptions, such as alert window or synchronous ajax call).