I'm newbie about angular testing and I'm challenging this scenario.
This function (not written by me) return a promise object:
GetByEmail: function (email) {
var def = $q.defer();
this.InvokeMyApi(
this.ApiParams.EntryPoints.Account.Mode,
this.ApiParams.EntryPoints.Account.GetState,
{},
{ Email: email }).then(
function (e) {
def.resolve(e.data);
}, function (e) {
def.reject(e);
});
return def.promise;
}
My goal is to test it with Qunit so I'm trying to read the promise content with this one:
var promise = GetByEmail(email);
promise.then(function (data) {
console.log('successo ', data);
}, function (error) {
console.log('errore ', error);
});
It seems so simple but nothing happens when I try to run the second function.
Any help for me?
Related
I am using pdf.js. I want to write a wrapper that returns angular promise instead of the native javascript promise returned by pdf.js. Here is my code so far.
this.hasPassword = function (url) {
return PDFJS.getDocument(url, null, null, null).then(function (res) {
if (res.pdfInfo) {
return false;
}
return true;
}, function (err) {
return true;
});
};
This return's a javascript promise. How do I make it return angular promise. Can I use $q.defer() and resolve it and return angular promise?
I guess you want to do smth like this:
function (url) {
var deferred = $q.defer();
PDFJS.getDocument(url, null, null, null).then(function (res) {
if (res.pdfInfo) {
deferred.resolve(false);
}
deferred.resolve(true);
}, function (err) {
deferred.reject(true);
});
return deferred.promise;
};
I have a request function :
function search(request) {
return $http.post('/path/to/resource', request);
}
I can call it like this :
search({/*...*/})
.success(function() {})
.error(function() {})
As I often need to find some objects by their ID, I need a shortcut function. I cannot find how to create this function so that I can also chain it with success() and error() functions.
I searched how to create a promise in angular and found the documentation about $q and this is what I tried :
function searchById(id) {
var deferred = $q.defer();
search({id: id}).
then(function (response) {
deferred.resolve(response.data.results[0]);
}, function (error) {
deferred.reject(error);
});
return deferred.promise;
}
I can only call it like this :
searchById().then(successCallback, errorCallback);
I would like to be able to call it like this :
searchById()
.success(successCallback)
.error(errorCallback);
The documentation about $q indicates that it returns a promise whereas the documentation about $http indicates that it returns an httpPromise but I cannot figure how to create an httpPromise.
Any idea?
In angular promises the error callback should be catch not error, try this
searchById()
.then(successCallback)
.catch(errorCallback);
sjokkogutten is correct that you don't need to use $q in this case you can simplify this
var deferred = $q.defer();
search({id: id}).
then(function (response) {
deferred.resolve(response.data.results[0]);
}, function (error) {
deferred.reject(error);
});
return deferred.promise;
to this
return search({id: id}).
then(function (response) {
return response.data.results[0];
}
$http is already returning a promise, so there is no need to use $q.defer(). Also, success() and error() has been depreciated (since 1.4.4), you should use then() instead.
Call your function like this:
search(request).then(function(data){
// successcallback
}, function(error){
// errorcallback
})
Creating a factory with $http functions will allow you to use .success and .error. But you really should be using .then.
app.factory("dataService", function($http) {
return {
search: function() {
return $http.get('path/to/api');
},
searchById: function(payload) {
return $http.post('path/to/api', payload);
},
searchMoreThings: function(payload) {
if(payload === "foo") payload = "bar";
return $http.post('path/to/api', payload);
}
}
});
You can do:
dataService.searchById(searchTerm).success().error();
Here is an actual example :
app.controller('controller',function($scope,$rootScope,$http,){
$scope.login = function(request){
var promise = $http.post(/path/to/resource, request, {
headers: {
'Content-Type': 'application/json'
}
}).then(function success(res){
//it worked, you have data in res
},function error(res){
// it failed
});
};
});
I am trying to write a test case for one of my REST Apis using mocha.
My Rest api looks like this:
server.route({
method : "DELETE",
path : "/local/{id}",
handler: function (request, reply) {
var id = request.params.id;
return getId(id)
.then(function (result) {
return testFunction(result, id, reply);
})
.catch (function (err) {
reply(400).err;
})
}
function testFunction(result, id, reply) {
return update(id, result)
.then(function (resp) {
reply(resp);
stopSomething(id)
.then(function () {
//do some work
return Bluebird.resolve(data);
})
.catch(function (error) {
//handle error & just log..user does not need to know
//stopSomething is a promise chain which runs in background
});
})
.catch(function (err) {
//handle error and reply to user
});
}
});
To test this I wrote the following test case:
describe("with an valid id", function () {
var stopSomethingStub;
var result
before(function () {
stopSomethingStub = Sinon.stub(stopSomethinbObject, "stopSomething", function () {
return Bluebird.resolve();
});
return new Request("DELETE", "/local/id123").inject(server)
.then(function (data) {
result = data;
});
});
after(function () {
//do clean up of stubs
});
it("deletes id", function () {
expect(result.statusCode).to.equal(200);
expect(stopSomethingStub.called).to.be.true;
//Few more checks
});
}
Right now, the "it" block executes immediately after receiving the 200 Ok response for the DELETE Request. However I would like it to finish the entire promise chain before checking the assertion. stopSOmethingStub.called is shown as false if I keep it as the first expect block. However if I keep it as the last assertion it works. I think this is some timing issue.
I have some logic where I'm trying to build a "person" object and I have a method called getProvider where i'm basically fetching a provider from mongo and assiging it to person.provider.
Since the call to mongo is async, i'm trying to use promises. The value seems to be correct everywhere i've logged it except in my .then method. It shows {"source": {}} and I have no idea why.
Here's my output:
data =
MYPROVIDER
provider =
MYPROVIDER
after addProvider
{"source":{}}
...
configDone.promise.then(function () {
return addProvider();
}).then(function() {
console.log('after addProvider');
console.log(JSON.stringify(people[0].provider)); // SHOWS {"source": {}}. WHY IS IT DOING THIS?
callback(null, people);
}).fail(function (err) {
callback(err);
});
function addProvider() {
var promises = [];
people.forEach(function(person){
promises.push(person.provider = getProvider(person.id, db));
});
return Q.all(promises).spread(function(provider) {
console.log('provider = ');
console.log(provider); // SHOWS CORRECT PROVIDER HERE
});
}
// Gets a provider for a specific person id
var getProvider = function(id, db) {
var def = Q.defer();
db.collection('providerLists').findOne({id: id}, function (err, data) {
if (err) {
def.reject(err);
} else {
console.log('data = ');
console.log(data.ListName); // SHOWS CORRECT VALUE
def.resolve(data.ListName);
}
});
return def.promise;
}
people[0].provider should be pointing at a resolved promise. If you attach a then handler to it you will see the expected provider value:
people[0].provider.then(function(provider) {
console.log(JSON.stringify(provider);
});
If you wanted to have the provider to be a value on each person of the people array you could update your addProvider function as follows:
function addProvider() {
var promises = [];
people.forEach(function(person){
promises.push(getProvider(person.id, db)
.then(function(provider) {
person.provider = provider;
});
});
return Q.all(promises).spread(function(provider) {
console.log('provider = ');
console.log(provider); // SHOWS CORRECT PROVIDER HERE
});
}
I am having some difficulties with promises when it comes chaining multiple ones. The confusion is distinguishing how to properly take advantage of promises & their difference with Callbacks. I noticed that callbacks sometime fire regardless a promise is resolved or not, making the below implementation unreliable..(Unless my syntax & logic are wrong) I read the official documentation and came up with this, but I am not sure it is well implemented.The Registration flow is as follow:
User chooses an Alias -> Details Alias + userID (Device's Universally Unique Identifier) are sent server side
If Alias is available, ApiKey(token) is generated, User registered and sent back client side (Stored in DB)
Services.js
(function(angular) {
myApp.factory("deviceDB.Service", ['$resource', '$http', '$q',
function ($resource, $http , $q ) {
return {
//Second Promsie : After API token is generated server-side, store res in db
RegDevice: function (alias, apiKey, userID) {
var deferred = $q.defer();
var configuration ;
var db = window.sqlitePlugin.openDatabase({name: "config.db"});
setTimeout(function () {
db.transaction(function (tx) {
tx.executeSql('CREATE TABLE IF NOT EXISTS user_details (userID UNIQUE , alias TEXT, apiKey TEXT)');
tx.executeSql("INSERT INTO user_details (userID, alias, apiKey) VALUES (?,?,?)", [userID, alias, apiKey], function (tx, res) {
deferred.resolve(configuration = true);
}, function (e) {
// console.log("ERROR: " + e.message);
deferred.reject(configuration = false);
});
});
}, 1000);
return deferred.promise;
},
//First Promsie: Register user server side & generate APi token
RegUser: function (alias, userID) {
var deferred = $q.defer();
var pro;
pro = $resource('api/query/register', {'alias': alias, 'userID': userID},
{ query: {
isArray: false,
method: 'GET' } });
setTimeout(function () {
pro.query(function (res) {
if (res.error) {
deferred.reject( { error : res.error, exists: res.exists, msg: res.message } );
}
else {
deferred.resolve( {error : res.error , alias: res.alias , apiKey: res.apiKey, msg: res.message } );
}
}, function (e) {
deferred.reject( { errorStatus: e.status } );
});
}, 1000);
return deferred.promise;
}
};
}]);
}(window.angular));
Now, in My controller I would like to chain both promises above. I quote the follwoing from the Documentation :
then(successCallback, errorCallback, notifyCallback) – regardless of when the promise was or will be resolved or rejected, then calls one of the success or error callbacks asynchronously as soon as the result is available. The callbacks are called with a single argument: the result or rejection reason. Additionally, the notify callback may be called zero or more times to provide a progress indication, before the promise is resolved or rejected.
What is the point of having Callbacks if they can fire regardless if the Promise is resolved?
Shouldn't I call for e.g Promise2 within the first Promise's Success Callback? If it is fired regardless of of Promise1 being resolved, How then can I chain Promise2 in a way to fire only when Promise1 is resolved?
What I tried :
Controller.js
myApp.controller('RegisterController', ['$scope', '$http', 'deviceDB.Service',
function ($scope , $http , deviceDB.Service) {
var Promise1 = deviceDB.RegUser($scope.alias, $scope.Device);
// First promise - Validate with server
Promise1.then(function(data)
{
console.log(' Registration Server-Side successfully');
$scope.apiKey = data.apiKey;
term.echo(data.apiKey);
}, function(e)
{
console.log('Registration Failed');
term.echo(e.msg);
})
//Call Promise 2 & Store details Client-Side using .then()
.then(deviceDB.RegDevice($scope.alias, $scope.apiKey, $scope.Device),
function(d){
console.log('Items Stored in DB successfully');
}, function()
{
console.log('Items Stored in DB Failed');
});
}]);
Notes: I understand it is a bad practice to store details client-side, however, i am after a different concept (anonymous messaging) and there is no security concerns..
Thanks for your time
Your second then call seems incorrect, after
//Call Promise 2 & Store details Client-Side using .then()
then takes up-to 3 parameters then(successCallback, errorCallback, notifyCallback) you are passing it: deviceDB.RegDevice($scope.alias, $scope.apiKey, $scope.Device) which is evaluated immediately and the promise returned is passed to the function then as the success function, your success function is passed as the errorCallback and your fail function is passed as the notifyCallback.
I would try the following
Promise1.then(function(data)
{
console.log(' Registration Server-Side successfully');
$scope.apiKey = data.apiKey;
term.echo(data.apiKey);
return deviceDB.RegDevice($scope.alias, $scope.apiKey, $scope.Device)
}, function(e)
{
console.log('Registration Failed');
term.echo(e.msg);
return e;
}).then(function(d) {/*all good*/}, function(e) {/* all bad */}
Notice the call to RegDevice is now within a function block, and a promise is returned from the then block you want to chain from.
I find $q.serial a great library for chaining promises. It's very easy to use and handles a lot of stuff like checking if all promises on the chain are really promises.
Here is a small example:
function do_all() {
var task_1 = function() {
return $http.get("some url")
.then(on_xhr_completed_fn, on_xhr_failed_fn);
}
var task_2 = function(some_data) {
vm.bla = some_data;
return $http.get("other url")
.then(on_xhr_completed_fn, on_xhr_failed_fn);
}
var task_3 = function(other_data) {
vm.bli = other_data;
}
var tasks = [task_1, task_2, task_3];
return $q.serial(tasks)
.then(function() {
console.log("Finished tasks 1, 2 and 3!!!");
});
}
Here's an approach that may be helpful using async/await:
async function run_promise_A(args) {
return new Promise((resolve, reject) => {
return resolve(resolve_value)
});
}
async function run_promise_B(args) {
return new Promise((resolve, reject) => {
return resolve(resolve_value)
});
}
async function run_promise_C(args) {
return new Promise((resolve, reject) => {
return resolve(resolve_value)
});
}
async function run_several_async_functions(userid) {
let a = run_promise_A(userid);
let b = run_promise_B(a);
let c = run_promise_C(b);
return c;
}
return Promise.resolve()
.then(() => {
let c = (async () => {
let c = await run_several_async_functions(userid)
return c;
})();
return c;
})
.then((c) => {
return c;
})
.catch((err) => {
console.log(err);
});