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.
Related
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...
})
;
}
I've been trying to write better code on my node.js server and after reading some blog posts like the following:
http://www.codelord.net/2015/09/24/$q-dot-defer-youre-doing-it-wrong/ (angular specific but same concept)
http://bahmutov.calepin.co/linking-promises.html
I am still not sure if I'm returning my data down the promise chain the "right way".
I cannot tell when it's appropriate to return or pass data down a promise like this
case 1
var promise = function () {
var defer = q.defer();
var myData = "hi"
defer.resolve(myData);
return d.promise;
};
or like this.
case 2
var promise = function () {
var myData = "hi"
return myData;
};
I'm assuming is that if I know something will be returned where it's not possible for the promise chain to break then use case 2 but if their is a change it could fail (i.e. it's returning data from a network call but the user is offline) then use case 1 so it can handle both cases. Is that correct or do I have a misunderstanding about how this flow/process works.
In both cases you are returning a result which is instantly known or computed, while you are wrapping it in a promise in the first case. Whether or not you want to do this depends on whether it should be transparent for the caller of your function if the result is computed asynchronously or not. If you return a promise you are free to change the implementation of your function later to compute or retrieve the result asynchronously.
Two hints:
You are not using a promise in case 2 so don't call your function promise.
In the first case you can just return q("hi"); to wrap the literal in a promise.
promise is for those who engage callback hell, which means your jobs are Asynchronous and chained
for a simple case like $.ajax().success(callback1).fail(callback2) is type of promise
3.your case is not Asynchronous, it might be like this:
var promise1 = function() {
//async get file name
}
var promise2 = function(filename) {
var defer = q.defer();
var myData = "hi"
//async
fs.readFile(filename, 'utf-8', function(err, data) {
if (err) {
defer.reject(err);
} else {
defer.resolve(myData);
}
}
}
return d.promise;
};
var promise3 = function(data) {
//use data do something
}
var onError(err) {
console.log(err);
}
var onDone(result) {
console.log(result)
}
//your logic might look like
promise1.then(promise2).then(promise3).catch(onError).done(onDone);
I understand Angular promises, when Angular makes request with $http, the .then(function(res){ return res }) is supposed to wait until the promise is resolved. Therefore, if I attached the request to a variable, shouldn't the variable's value be that resolved promise?
Service to call the Github API
is.service('githubApiService', function($http) {
this.repo = function(user, repo) {
var value = $http.get('https://api.github.com/repos/' + user + '/' + repo).then(function(json) {
return {
stargazers_count: json.data.stargazers_count,
watchers_count: json.data.watchers_count,
forks_count: json.data.forks_count,
watchers: json.data.watchers,
subscribers_count: json.data.subscribers_count
};
});
return value;
};
})
Directive that calls the Service
is.directive('chart', function(githubApiService) {
return {
restrict: 'E',
template: '<svg margin="[20, 30, 30, 40]", width="750" height="450"></svg>',
link: function(scope, element, attrs) {
var ais = githubApiService.repo('srph', 'angular-infinite-scroll');
var ai = githubApiService.repo('thenikso','angular-inview');
var ns = githubApiService.repo('jankuca', 'ng-scroller');
console.log(ai); // returns a promise!
}
}
})
This is expected behavior. It returns the promise in all scenarios, you need to use .then() to get the data you want:
githubApiService.repo('thenikso','angular-inview').then(function(data){
var ai = data;
});
You can't get the value of the promise like you are expecting.
If you look at $q documentation, you will see that the then method that you are chaining returns also a promise:
This method returns a new promise which is resolved or rejected via the return value of the successCallback, errorCallback (unless that value is a promise, in which case it is resolved with the value which is resolved in that promise using promise chaining)
You therefore have to get the returned value through a success handler:
githubApiService.repo(...).then(function (data) {
// data is the object you are expecting
});
It returns the promise because you do not know when the underlying asynchronous call will be completed.
In some trivial cases this may happen almost immediately, but until you explicitly 'resolve' the fulfilled or rejected promise via the then method, you just have the actual promise.
Consider these two methods where doSomething returns a promise:
myService.doSomething().then(function(data){
return data;
});
myService.doSomething().then(function(data){
//This is some non-trivial calculation that will take a few seconds to do
var myData = transformData(data);
return myData;
});
The promise allows you to deal with both these calls the same way, where you have to resolve it with a then method call and not actually worry if the data is ready or not at that point in your program.
I am new at AngularJs and very new at Typescript.
I included Typescript in my AngularJs project but couldn't handle a service where i return a $q(function(){...})
my code looks like:
function foo(request, monitor, currentMonitorPropertys) {
var currentChart;
return $q(function (resolve) {
$http(request).success(function (chartResponse) {
...
resolve(monitor);
}).error(function(response){
...
});
});
I work with VS2013(TypeScript), if i implement this method like above, there comes an compilererror: Value of type 'IQService' is not callable. Did you mean to include 'new'?
So how could I implement the function with Typescript.
Thank you for your answer.
There are several ways to return a promise... $http returns a promise with each of its ajax calls, $timeout also returns a promise.
That being said you want to return a promise based upon something other than a scheduled event ($timeout, $interval) via $q you can do this...
// assume $q is injected into your service/controller/factory
// create a defer object
var defer = $q.defer();
// do something...
if (doSomething()){
defer.resolve(); //something went right
else {
defer.reject(); //something went wrong
}
//make sure you return out the promise, so the consumer can act upon it.
return defer.promise;
Also, $q has some nice helper methods to return a promise that you can use when you stub out some logic;
// this will a promise that will resolve with the value provided
return $q.when({some: 'result'});
// this will return a promise that will reject with the error specified
return $q.reject('some error message');
$q isn't a function but a service. If you want a defer, you can use the following code for example:
var deferred = $q.defer();
$http(request).success(function (chartResponse) {
deferred.resolve(monitor);
}).error(function(response){
deferred.reject(response);
});
// return promise
return deferred.promise;
What you can keep in mind if you don't do anything else, you can just return the $http call, because it is a promise itself:
return $http(request);
As you're using $http (that already returns a promise) why not returning this promise directly? Simpler and faster.
function foo(request, monitor, currentMonitorPropertys) {
var currentChart;
return $http(request).then(function (chartResponse) {
//...
return monitor;
});
});
Then when consuming this service you could manage success and error from there, which makes for a tidier implementation:
// for ex. in a controller
foo().then(mySuccessCallback)
.catch(myErrorHandler)
You need to defer $q before resolve it This is code try this one
(function retriveDemoJsonData(){
angular.module('myApp').factory('actionData', function ($q, $http) {
var data={};
data.actionDataJson = function(id){
//The original business logic will apply based on URL Param ID
var defObj = $q.defer();
$http.get('demodata.json')
.then(function(res){
defObj.resolve(res.data[0]);
});
return defObj.promise;
}
return data;
});
})();
----------+
I hope this will help you......
I am using the AngularJS implementation of $q.
Given the following functions:
doTask1: function ($scope) {
var defer = $q.defer();
$http.get('/abc')
.success(function (data) {
defer.resolve();
})
.error(function () {
defer.reject();
});
return defer.promise;
},
doTask2: function ($scope) {
var defer = $q.defer();
var x = 99;
return defer.promise;
},
I know I can delay execution of another function like this:
os.doTask1()
.then(function () {
doTask3();
});
I would like to start off doTask1 and doTask2 at the same time.
Is there a way I can do this and still delay execution so that
doTask3() will not execute until doTask1 and doTask2 have ended
with success.
$q.all is what you're looking for. (Documentation)
$q.all takes multiple promises, either in an array or object, and returns a brand new promise that will be resolve when all the passed-in promises are resolved. If you passed in an array of promises, the new promise is resolved with an array of values which correspond to the promises:
$q.all([doTask1(), doTask2()]).then(function(results) {
// results[0] == result of doTask1
// results[1] == result of doTask2
doTask3();
});
If you passed in an object of key-promise pairs, it will be resolved with an object with keys that match the object you passed in, each value nicely corresponding to the value of that key's resolved promise:
$q.all({one: doTask1(), two: doTask2()}).then(function(results) {
// results.one == result of doTask1
// results.two == result of doTask1
doTask3();
});
Because of promise chaining (e.g., when you return a promise from a then function, it creates a new promise that resolves to the resolved value of the promise you returned), you can do some cool stuff:
var bothPromises = $q.all([doTask1(), doTask2()]);
var task3Promise = bothPromises.then(function(results) {
var result1 = results[0];
var result2 = results[1];
return doTask3(result1, result2);
});
task3Promise.then(function(resultOfDoTask3) { ... });
It is worth nothing that if any of the promises passed to $q.all is rejected, the promise that is returned will also be rejected. See the Angular $q documentation for more information.
This is (barely) tangential to the question, but I think it's a neat trick: if you happen to use CoffeeScript, you can use destructuring to get to the promises' resolved values.
For arrays:
$q.all([doTask1(), doTask2()]).then ([result1, result2]) ->
# ...
And for objects
$q.all(one: doTask1(), two: doTask2()).then ({one, two}) ->
# note that you have to use the same key names
# as in the object you passed to `$q.all`