I have the following javascript function :
function render(id) {
var deferred = $q.defer();
Flights.get(id).then(function(flightDto){
Arrivals.getDemoProfile(flightDto.id).then(function(arrivalDto) {
self.arivalId = arrivalDto.id;
deferred.resolve(self);
});
});
return deferred.promise;
}
Is there any way I can simplify better using promise so that the promise only resolves after the arrivals call is made? I am using angular and the built in $q library.
function render(id) {
return Flights.get(id).then(function(flightDto) {
return Arrivals.getDemoProfile(flightDto.id).then(function(arrivalDto) {
self.arivalId = arrivalDto.id;
return self;
});
});
}
Anything you return inside a then will be treated as the resolve of that promise.
Unless you return a promise, in which case that will be waited for, and the result will be treated as the resolve of that promise.
This means you can nest the then as deeply as you need and just keep returning from the nested functions.
The great thing about promises is that they can be chained instead of being nested. This makes the code a lot clearer and easier to reason (about which comes first for example). Following is the code from Buh Buh's answer, improved to chain the second promise instead of nesting:
function render(id) {
return Flights.get(id)
.then(function(flightDto) {
return Arrivals.getDemoProfile(flightDto.id);
})
.then(function(arrivalDto) {
self.arivalId = arrivalDto.id;
return self; // I AM NOT SURE ABOUT THE USEFULNESS OF THIS LINE...
})
;
}
Related
I have seen answers on StackOverflow where people suggest furnishing a callback function to an AngularJS service.
app.controller('tokenCtrl', function($scope, tokenService) {
tokenService.getTokens(function callbackFn(tokens) {
$scope.tokens = tokens;
});
});
app.factory('tokenService', function($http) {
var getTokens = function(callbackFn) {
$http.get('/api/tokens').then (function onFulfilled(response) {
callbackFn(response.data);
});
};
return {
getTokens: getTokens
};
});
This seems to me to be an Anti-Pattern. The $http service returns promises and having .then methods execute callback functions feels like an unhealthy inversion of control.
How does one re-factor code like this and how does one explain why the original way was not a good idea?
You should change it to
var getTokens = function() {
return $http.get('/api/tokens');
};
And, then in other module use
yourModule.getTokens()
.then(function(response) {
// handle it
});
As to why it's an anti-pattern, I'd say that, first, it doesn't allow you to further chain your success/fail handler methods. Second, it handles the control of processing the response from caller-module to called module (which might not be super-important here, but it still imposes same inversion of control). And finally, you add the concept of promises to your codebase, which might not be so easy to understand for some of the teammates, but then use promises as callbacks, so this really makes no sense.
The code can be re-factored as follows:
app.controller('tokenCtrl', function($scope, tokenService) {
tokenService.getTokens.then ( callbackFn(tokens) {
$scope.tokens = tokens;
});
});
app.factory('tokenService', function($http) {
var getTokens = function() {
//return promise
return $http.get('/api/tokens').then (function onFulfilled(response) {
//return tokens
return response.data;
}
);
};
return {
getTokens: getTokens
};
});
By having the service return a promise, and using the .then method of the promise, the same functionality is achieved with the following benefits:
The promise can be saved and used for chaining.
The promise can be saved and used to avoid repeating the same $http call.
Error information is retained and can be retrieved with the .catch method.
The promise can be forwarded to other clients.
I'm consuming an API that returns JSON, and on this page I have a progress bar indicating various steps toward setting something up at the user's request. Each subsequent AJAX request's success callback initiates the next request, because they need to be done in sequence. One step issues a server-side background job and the endpoint returns a transaction ID.
Outside this flow there is a function that checks another endpoint to see if this transaction is complete or not. If it's "pending", I need to reissue the request after a small delay.
I had this working with a recursive function:
function checkTransaction(trxid) {
window.profileTrx[trxid] = 0;
trxurl = 'https://example.com/transaction/'+trxid;
$.getJSON(trxurl,function(result) {
if(result.status === 'pending') {
setTimeout(function () {
checkTransaction(trxid);
},3000);
} else {
window.profileTrx[trxid] = result;
}
});
}
The reason I was using window is so I could access the transaction by its ID in the callback it came from - a good use case for a promise if ever there were one. But it got messy, and my lack of experience began to get in my way. Looping over the state of window.profileTrx[trxid] seemed like double work, and didn't behave as expected, looping too quickly and crashing the page. Again, a promise with the next step in .then() was my idea, but I can't figure out how.
How could I implement this with promises such that the callback function that initiated the recursive "transaction check" would only continue with the rest of its execution once the API returns a non-pending response to the check?
I could get my head round recursing, and returning a promise, but not both at once. Any and all help massively appreciated.
My head is always clearer when I factor out promises first:
// wrap timeout in a promise
function wait(ms) {
var deferred = $.Deferred();
setTimeout(function() {
deferred.resolve();
}, ms);
return deferred.promise();
}
// promise to get a trxid
function getTRX(trxid) {
var trxurl = 'https://example.com/transaction/'+trxid;
return $.getJSON(trxurl);
}
Now the original function seems easy...
function checkTransaction(trxid) {
window.profileTrx[trxid] = trxid;
return getTRX(trxid).then(function(result) {
if (result.status === 'pending') {
return wait(3000).then(function() {
return checkTransaction(trioxid);
});
} else {
window.profileTrx[trxid] = result;
return result;
}
});
}
The caller will look like this:
return checkTransaction('id0').then(function(result) {
return checkTransaction('id1');
}).then(function(result) {
return checkTransaction('id2');
}) // etc
Remember, if the checkTransaction stays pending for a very long time, you'll be building very long chains of promises. Make sure that the get returns in some very small multiple of 3000ms.
"deferred"-based solution (not recommended)
Since you are using jQuery in your question, I will first present a solution that uses jQuery's promise implementation based on the $.Deferred() object. As pointed out by #Bergi, this is considered an antipattern.
// for this demo, we will fake the fact that the result comes back positive
// after the third attempt.
var attempts = 0;
function checkTransaction(trxid) {
var deferred = $.Deferred();
var trxurl = 'http://echo.jsontest.com/status/pending?' + trxid;
function poll() {
console.log('polling...');
// Just for the demo, we mock a different response after 2 attempts.
if (attempts == 2) {
trxurl = 'http://echo.jsontest.com/status/done?' + trxid;
}
$.getJSON(trxurl, function(result) {
if (result.status === 'pending') {
console.log('result:', result);
setTimeout(poll, 3000);
} else {
deferred.resolve('final value!');
}
});
// just for this demo
attempts++;
}
poll();
return deferred.promise();
}
checkTransaction(1).then(function(result) {
console.log('done:', result)
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
This should work (run the snippet to see), but as mentioned in the linked answer, there are issues with this "deferred" pattern, such as error cases not being reported.
The issue is that jQuery promises (until possibly recent versions - I've not checked) have massive issues that prevent better patterns from being used.
Another approach would be to use a dedicated promise library, which implements correct chaining on then() functions, so you can compose your function in a more robust way and avoid the "deferred" antipattern:
Promise composition solution (better)
For real promise composition, which avoids using "deferred" objects altogether, we can use a more compliant promise library, such as Bluebird. In the snippet below, I am using Bluebird, which gives us a Promise object that works as we expect.
function checkTransaction(trxid) {
var trxurl = 'http://echo.jsontest.com/status/pending?' + trxid;
var attempts = 0;
function poll() {
if (attempts == 2) {
trxurl = 'http://echo.jsontest.com/status/done?' + trxid;
}
attempts++;
console.log('polling...');
// wrap jQuery's .getJSON in a Bluebird promise so that we
// can chain & compose .then() calls.
return Promise.resolve($.getJSON(trxurl)
.then(function(result) {
console.log('result:', result);
if (result.status === 'pending') {
// Bluebird has a built-in promise-ready setTimeout
// equivalent: delay()
return Promise.delay(3000).then(function() {
return poll();
});
} else {
return 'final value!'
}
}));
}
return poll();
}
checkTransaction(1).then(function(result) {
console.log('done:', result);
})
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/bluebird/3.4.1/bluebird.min.js"></script>
You can return promises from functions, and the .then of the parent function will resolve when all the returned promises are resolved.
check this out for full details.
https://gist.github.com/Bamieh/67c9ca982b20cc33c9766d20739504c8
I'm studying the promises with Nodejs.
I've a question about a situation with two nested Q.all.
Q.all(promises1)
.then(function(res1) {
var promises2 = <METHOD THAT USE THE RES1>
Q.all(promises2)
.then(function(re2) {
...
})
})
The problem is that I need the result of the first Q.all for the second.
The promises1 are multiple save function, and I need the objectId of the item saved in the multiple function that I use in the promises2.
I'am studying the promises for not having nested function, my question is how can I resolve this nest?
You can return a promise from within a then function to continue a chain. Also I want to note that the built in Promise API is supported by node.
Promise.all(promises1)
.then(res => {
let promises2 = [];
return Promise.all(promises2);
})
.then(res => {
});
how can I resolve this nest?
As always :-) You need to return the promise from the then callback, in this case the one you got with the second Q.all call, and then you can chain your second then invocation to the outer promise:
Q.all(promises1)
.then(function(res1) {
var promises2 = … // method that uses `res1`
return Q.all(promises2)
// ^^^^^^
}).then(function(res2) {
…
});
I'm using q promises, and I want to show the spinners when promise starts. Currently I'm doing this way:
getPromise().then(function() { spinner.hide() })
and in the getPromise() fn, I'm showing up the spinner, so getPromise looks like:
function getPromise()
{
spinner.show()
}
But is there any way to intercept the then block in q, so that I can add the spinner.show to that intercept?
You are over-thinking it
var spinOnPromise = function(p) {
spinner.show()
p.finally(function() {
spinner.hide()
});
return p;
}
Pass in the promise, and the spinner will go as long as the promise is pending.
Edit: you could do this:
var spinOnPromise = function(p) {
spinner.show()
return p.finally(function() {
return spinner.hide()
});
}
If you do this this, the difference is, if spinner.hide() returns a promise (call it p1), the promise returned from spinOnPromise() will not be resolved until p1 is resolved, but it will resolve to the same value as p. See here for details.
You could do this, but I don't see offhand why you would.
I'm trying to get my head around promises in JavaScript (in particular AngularJS).
I have a function in a service, let's call it fooService, that checks if we've loaded some data. If it has, I just want it to return, and if we haven't, we need to load the data and return a promise:
this.update = function(data_loaded) {
if (data_loaded) return; // We've loaded the data, no need to update
var promise = Restangular.all('someBase').customGet('foo/bar').then(function(data) {
// Do something with the data here
}
return promise;
}
I have another function that then calls the update function of fooService like so:
fooService.update(data_loaded).then(function() {
// Do something here when update is finished
})
My issue here is that if we don't need to load the data in the update function, a promise isn't returned, so the .then() is not called in my other function. What should the approach be here - basically I want to return a resolved promise immediately from the update() function if we do not need to get data from the Restangular call?
As your promise use the same syntax as the JavaScript native one, you could use and return an already resolved JavaScript promise : Promise.resolve()
return(Promise.resolve("MyReturnValue"));
The current accepted answer is overly complicated, and abuses the deferred anti pattern. Here is a simpler approach:
this.update = function(data_loaded) {
if (data_loaded) return $q.when(data); // We've loaded the data, no need to update
return Restangular.all('someBase').customGet('foo/bar')
.then(function(data) {
// Do something with the data here
});
};
Or, even further:
this._updatep = null;
this.update = function(data_loaded) { // cached
this._updatep = this._updatep || Restangular.all('someBase') // process in
.customGet('foo/bar'); //.then(..
return this._updatep;
};
AngularJS's $q service will help you here. It is much like Kris Kowal's Q promise library.
When you have an async method that may return a promise or value use the $q.when method. It will take what ever is passed to it, be it a promise or a value and create a promise that will be resolved/rejected based on the promise passed, or resolved if a value is passed.
$q.when( fooService.update(data_loaded) ).then(function(data){
//data will either be the data returned or the data
//passed through from the promise
})
and then in your update function return the data instead of just returning
if (data_loaded) return data_loaded;
Similar to Elo's answer, you can return an already resolved promise using the async/await syntax:
this.update = async (data_loaded) => {
if (data_loaded)
return await null; // Instead of null, you could also return something else
// like a string "Resolved" or an object { status: 200 }
else
return await OtherPromise();
}
You could use the $q.defer() like this:
this.update = function (data_loaded) {
var deferred = $q.defer();
if (data_loaded) {
deferred.resolve(null); // put something that your callback will know the data is loaded or just put your loaded data here.
} else {
Restangular.all('someBase').customGet('foo/bar').then(function(data) {
// Do something here when update is finished
deferred.resolve(data);
}
}
return deferred.promise;
};
Hope this helps.