Simple chain of promisified function wrapper? - promise

For example A is an existing object with API following node.js convention:
function A() {
}
A.prototype.op = function (cb) {
cb(undefined, 'success');
};
A.prototype.op2 = function (cb) {
cb(undefined, 'success 2');
};
A.prototype.log = function(r) {
console.log(r);
};
When I do Promise.promisifyAll(A.prototype) I will get generated *Async() functions.
I would like to have a readable chain like this:
Promise.bind(a)
.then(a.opAsync)
.then(a.op2Async)
.then(a.log);
I know that this doesn't work because we need additional function wrapper returning promise:
Promise.bind(a).then(function() {
return this.opAsync();
}).then(function(){
return this.op2Async();
}).then(function(r) {
this.log(r);
});
Do I have to write wrapper for every promisified function? Or there is a better way to design this API?

Adding a dummy parameter to original function resolved the problem.
A.prototype.op2 = function (x, cb) {
cb(undefined, 'success 2');
};
Working example
Issue with details

Related

Promise proces one by one (Sequential)

function one_by_one(objects_array, iterator, callback) {
var start_promise = objects_array.reduce(function (prom, object) {
return prom.then(function () {
return iterator(object);
});
}, Promise.resolve()); // initial
if(callback){
start_promise.then(callback);
}else{
return start_promise;
}
}
one_by_one(requestBodyAll,task);
I tried the above code but it processed only the first file. Can someone help please?
requestBodyAll - contains the files list to process.
task - function returns promise.
Looks about right. If the reduction is not progressing past the first file, then it's likely that task(first_file) throws. You can find out by logging errors.
Also, you're better off returning a promise from one_by_one and not passing a callback.
function one_by_one(objects_array, work) {
return objects_array.reduce(function (prom, object) {
return prom.then(function() {
return work(object);
});
}, Promise.resolve()); // initial
}
one_by_one(requestBodyAll, someWorkFn)
.then(task)
.catch(function(error) {
console.log(error);
});
one_by_one() will (as in the question) deliver the result of iterator(last_file) as an argument to task. You may prefer to pass nothing, in which case write :
.then(function() {
return task();
})
You could immunize the process against errors by catching inside one_by_one().

.then is not a function Angularjs factory

I just started learning Jasmine test cases for angularjs. I am unable to test below code.Kindly help
$scope.getConstants = function(lovName) {
ConstantService.getConstants(lovName).then(function(d) {
switch (lovName) {
case 'WORKFLOW':
$scope.workflowTypes = d;
$scope.loadCounterpartyTmp();
break;
--------Other Cases
}
My ConstantService is defined as
App.factory('ConstantService', [ '$http', '$q', function($http, $q) {
return {
getConstants : function(lovName) {
return $http.post('/sdwt/data/getConstants/', lovName).then(function(response) {
return response.data;
}, function(errResponse) {
return $q.reject(errResponse);
});
}
I want to test getConstants function.I need to create a mock of ConstantService and pass the data to it.
I have written below test case but the test case is not working.Please let me know how to test the above code
describe('getConstantsForMurexEntity', function() {
it('testing getConstantsForMurexEntity function', function() {
var d=[];
d.push(
{id:1,value:'ABC'},
{id:2,value:'DEF'},
{id:3,value:'IJK'},
{id:4,value:'XYZ'},
);
//defined controller
spyOn(ConstantService, 'getConstants').and.returnValue(d);
$scope.getConstants('WORKFLOW');
expect($scope.workflowTypes).toBe(d);
The above test case is not working as it is saying "ConstantService.getConstants(...).then is not a function".
Your ConstantService.getConstants() function returns a promise, which your actual code is using, with the .then() call. This means means that when you spy on it, you also need to return a promise, which you are not doing. Because you are not returning a promise, when your actual call tries to call .then(), it is undefined, which is the reason for the error message.
Also, you aren't using Array.push correctly.
Your test should probably look something like the following (note, this is untested):
describe('getConstantsForMurexEntity', function() {
it('should set workflowTypes to the resolved value when lovName is "WORKFLOW"', inject(function($q) {
var deferred = $q.defer();
spyOn(ConstantService, 'getConstants').and.returnValue(deferred.promise);
var d = [
{id:1,value:'ABC'},
{id:2,value:'DEF'},
{id:3,value:'IJK'},
{id:4,value:'XYZ'},
];
$scope.getConstants('WORKFLOW');
deferred.resolve(d);
$scope.$apply();
expect($scope.workflowTypes).toBe(d);
}));
});

Using Jasmine spyOn to mock function in Browserify module

I'm trying to run a unit test on a function (testFunc). testFunc calls another function (secondFunc) which I would like to mock. Can I mock secondFunc so that when it is called in the context of testFunc, the spiedOn version of secondFunc is called? If not, how should I reformat my browserify module to make it testable?
Currently the setup looks something like this:
app.js (Browserify Module)
module.exports = (function () {
function testFunc() {
secondFunc();
}
function secondFunc(){
console.log('not mocked!');
}
return {
testFunc, secondFunc
};
})();
test.js (Jasmine Test)
describe("testFunc", () => {
let app = require('./app');
beforeEach(() => {
spyOn(app, 'secondFunc');
});
it("should call secondFunc spy", () => {
app.testFunc();
expect(app.secondFunc).toHaveBeenCalled();
});
});
The way you have it now, the spyOn is replacing the secondFunc property on your returned object with a proxy, but your code calls the secondFunc function that is inside the closure of the anonymous function. There are several ways to restructure your code to better expose the functions.
You could structure your module this way:
exports.testFunc = function() {
exports.secondFunc();
}
exports.secondFunc = function(){
console.log('not mocked!');
}
which is a lot smaller, easier to read, and let you mock the secondFunc function.
The reason this is happening is because you are setting up a mock on the returned object, but the code is calling the internal function. What I've done in the past is something like this:
module.exports = (function () {
function testFunc() {
api.secondFunc(); // Call the API function, which is what is mocked
}
function secondFunc(){
console.log('not mocked!');
}
var api = {
testFunc, secondFunc
};
return api;
})();

Async call in beforeAll

Here are 2 samples of the same test. The only difference is that first one uses a promise in beforeAll block to assign a value to the variable while the second one assigns the value directly.
I raised a similar question Running spec after promise has been resolved with one of the comments pointing to this issue https://github.com/jasmine/jasmine/issues/412 which says that this is not supported in Jasmine. Has somebody figured out any workaround?
This fails with TypeError: Cannot read property 'forEach' of undefined
describe('Async car test', function () {
var cars;
beforeAll(function (done) {
// getCars() is a promise which resolves to ['audi', 'bmw']
getCars().then(function (data) {
cars = data;
console.log(cars) // ['audi', 'bmw']
done();
});
});
cars.forEach(function (car) {
it('car ' + car, function () {
expect(car).toBe(car);
});
});
});
This works fine
describe('Car test', function () {
var cars = ['audi', 'bmw'];
cars.forEach(function (car) {
it('car ' + car, function () {
expect(car).toBe(car);
});
});
});
Posting it as an answer, because I can't see things properly in comments.
I'm actually generating tests in my spec as well, and I'm using https://www.npmjs.com/package/jasmine-data-provider , I think you probably cannot generate it directly from resolved promise. And wrapping in another it doesn't work for you. This should work:
var using = require('jasmine-data-provider');
using(cars.forEach, function (car) {
it(car + ' should be' + car, function () {
expect(car).toBe(car);
});
});
This is not an issue with jasmine, it is an issue with your code.
beforeAll does not block subsequent code below the statement. it blocks code that is defined in it('should ...', (done)=>{...});
it('should have cars', (done) => {
cars.forEach(function (car) {
expect(car).toBe(car);
});
});
Since Jasmine does not support adding tests at runtime, the trick is to request the asynchronous data before starting Jasmine, and then using the retrieved data during runtime instead. This can be achieved with a singleton and programmatically starting Jasmine.
See here for a working example.
// car-collection.js
class CarCollection {
static load() {
return this.request()
then((data) => this.cars = data);
}
static request() {
// in practice this function would do something cooler
return Promise.resolve(['audi', 'bmw']);
}
}
modules.export = CarCollection;
Since CarCollection has methods that are static they will be shared across imports and this.cars will persist.
// launcher.js
const Jasmine = require('jasmine');
const CarCollection = require('./car-collection');
CarCollection.load()
.then(() => {
console.log(`car count is ${CarCollection.cars.length}`); // prints: car count is 2
const jasmine = new Jasmine();
jasmine.loadConfigFile(...); // path to jasmine.json
jasmine.execute();
});
An important step here is configure jasmine to know where to look for the test files. Either by loading a config or passing specifics into the execute function.
// car.spec.js
const CarCollection = require('./car-collection');
describe('test', function () {
CarCollection.cars.forEach((car) => {
it('test' + car, () => {
expect(car).toBe(car);
});
});
});
Now run node ./launcher.js and the tests should run.

Angular: update scope with async AJAX data from a factory

What's the recommended way to do this?
1.
factory.updater = function(varObjToBeUpdated){
$http.post('/url', {})
.success(function(data){
for (data_field in data)
varObjToBeUpdated[data_field] = data[data_field];
});
}
...
myFactory.updater($scope.varObjToBeUpdated);
Or 2.,
myFactory.updater().success(function(data, ..){
$scope.varObjToBeUpdated = data;
});
...
factory.updater = function(){
return $http.post('/url', {});
}
Is it ok to to pass a reference scope variable to a factory? I always thought factories as delivering data.
And what's wrong with the second method (if it's less acceptable)?
I prefer the second approach, as this allows you to just inject the service when you need it across multiple controllers. Use .then to continue the promise pattern:
myFactory.updater().then(function(data, ..){
$scope.varObjToBeUpdated = data;
});
app.factory('myFactor', function($http) {
return {
updater: function() {
return $http({'/url',}).then(function(result) {
return result.data;
});
}
}
});

Resources