So I have a series of functions I've defined in the service that upload an image to my Amazon s3 bucket, and I can console.log and alert in the service itself and everything is coming back correct.
However, now I want to return that promise to the controller so I can let the user know the upload is finished. I'm just not sure how I would do that. I've tried putting returns at the filereader.onload but then I get errors saying that what I've given back isn't a promise and such. Here's my code:
angular.module("testApp", [])
.controller("testCtrl", function($scope, amazonService) {
$scope.test = "leeroy jenkins";
$scope.upload = function() {
amazonService.uploadImage($('#file'));
}
})
.service("amazonService", function($http) {
var url = "/api/"
var uploadImageFilestream = function(obj) {
return $http({
method: "PUT",
url: url + "media/images",
data: obj
}).then(function(res) {
if (res.status === 200) {
alert("upload successful!");
}
return res;
});
}
var formatFileName = function(filename, newBase) {
//first, get the file extension
var extension = filename.substring(filename.lastIndexOf("."));
return newBase + extension;
}
this.uploadImage = function(obj) {
var file = obj[0].files[0];
var fileReader = new FileReader();
fileReader.onload = function(loaded) {
uploadImageFilestream({fileName: formatFileName(file.name, "test1"), fileBody: loaded.target.result});
}
fileReader.readAsDataURL(file);
}
})
I know that if I combined the uploadImageFilestream function with the uploadImage function it would work, but I'm not sure how to structure it with the promise in a separate function.
Use $q:
.service("amazonService", function($http, $q) {
var url = "/api/"
var uploadImageFilestream = function(obj) {
return $http({
method: "PUT",
url: url + "media/images",
data: obj
});
}
var formatFileName = function(filename, newBase) {
//first, get the file extension
var extension = filename.substring(filename.lastIndexOf("."));
return newBase + extension;
}
this.uploadImage = function(obj) {
var file = obj[0].files[0];
var fileReader = new FileReader();
var deferer = $q.defer();
fileReader.onload = function(loaded) {
uploadImageFilestream({fileName: formatFileName(file.name, "test1"), fileBody: loaded.target.result})
.then(function(res) {
if (res.status === 200) {
deferer.resolve();
alert("upload successful!");
}
return res;
});
}
fileReader.readAsDataURL(file);
return deferer.promise;
}
})
You should be using $q service of AngularJs to create deferred object and return promise.
I have modified the following code as to demonstrate way to use promises.
angular.module("testApp", [])
.controller("testCtrl", function($scope, amazonService) {
$scope.test = "leeroy jenkins";
$scope.upload = function() {
var promise = amazonService.uploadImage($('#file')); // call to function returns promise
promise.then(function(){ // when promise is resolved, desired data is passed
alert("success");
}).catch(function(error){ // when promise is rejected, related error object is passed
alert("failure");
});
}
})
.service("amazonService", function($http, $q) { // added $q service to handle promises
var url = "/api/"
var uploadImageFilestream = function(obj) {
return $http({
method: "PUT",
url: url + "media/images",
data: obj
}).then(function(res) {
if (res.status === 200) {
alert("upload successful!");
}
return res;
});
}
var formatFileName = function(filename, newBase) {
//first, get the file extension
var extension = filename.substring(filename.lastIndexOf("."));
return newBase + extension;
}
this.uploadImage = function(obj) {
var file = obj[0].files[0];
var fileReader = new FileReader();
var deferredObject = $q.defer(); // added deferred object which will be used to return promise and resolve or reject is as shown below
fileReader.onload = function(loaded) {
uploadImageFilestream({fileName: formatFileName(file.name, "test1"), fileBody: loaded.target.result}).then(response){
deferredObject.resolve(response); // when resolve function of deferred object is called success callback in controller will be called with the data you pass here
}).catch(function(errorObj){
deferredObject.reject(errorObj); // when reject function of deferred object is called error callback is controller will be called with the error object you pass here
});
}
fileReader.readAsDataURL(file);
return deferredObject.promise; // return promise object which will be resolve or reject and accordingly success callback and error callback will be called with then and catch respectively
}
});
Link to AngularJs Reference.
There are other different ways too to create and return promise you can see it in reference.
One other way to create and return promise as given in reference is using $q object as function and passing callback directly as shown below:
// for the purpose of this example let's assume that variables `$q` and `okToGreet`
// are available in the current lexical scope (they could have been injected or passed in).
// for the purpose of this example let's assume that variables `$q` and `okToGreet`
// are available in the current lexical scope (they could have been injected or passed in).
function asyncGreet(name) {
// perform some asynchronous operation, resolve or reject the promise when appropriate.
return $q(function(resolve, reject) {
setTimeout(function() {
if (okToGreet(name)) {
resolve('Hello, ' + name + '!');
} else {
reject('Greeting ' + name + ' is not allowed.');
}
}, 1000);
});
}
var promise = asyncGreet('Robin Hood');
promise.then(function(greeting) {
alert('Success: ' + greeting);
}, function(reason) {
alert('Failed: ' + reason);
});
Related
I want to write the unit test for the factory which have lot chain of promises. Below is my code snippet:
angular.module('myServices',[])
.factory( "myService",
['$q','someOtherService1', 'someOtherService2', 'someOtherService3', 'someOtherService4',
function($q, someOtherService1, someOtherService2, someOtherService3, someOtherService4) {
method1{
method2().then(
function(){ someOtherService3.method3();},
function(error){/*log error;*/}
);
return true;
};
var method2 = function(){
var defer = $q.defer();
var chainPromise = null;
angular.forEach(myObject,function(value, key){
if(chainPromise){
chainPromise = chainPromise.then(
function(){return method4(key, value.data);},
function(error){/*log error*/});
}else{
chainPromise = method4(key, value.data);
}
});
chainPromise.then(
function(){defer.resolve();},
function(error){defer.reject(error);}
);
return defer.promise;
};
function method4(arg1, arg2){
var defer = $q.defer();
someOtherService4.method5(
function(data) {defer.resolve();},
function(error) {defer.reject(error);},
[arg1,arg2]
);
return defer.promise;
};
var method6 = function(){
method1();
};
return{
method6:method6,
method4:method4
};
}]);
To test it, I have created spy object for all the services, but mentioning the problematic one
beforeEach( function() {
someOtherService4Spy = jasmine.createSpyObj('someOtherService4', ['method4']);
someOtherService4Spy.method4.andCallFake(
function(successCallback, errorCallback, data) {
// var deferred = $q.defer();
var error = function (errorCallback) { return error;}
var success = function (successCallback) {
deferred.resolve();
return success;
}
return { success: success, error: error};
}
);
module(function($provide) {
$provide.value('someOtherService4', someOtherService4);
});
inject( function(_myService_, $injector, _$rootScope_,_$q_){
myService = _myService_;
$q = _$q_;
$rootScope = _$rootScope_;
deferred = _$q_.defer();
});
});
it("test method6", function() {
myService.method6();
var expected = expected;
$rootScope.$digest();
expect(someOtherService3.method3.mostRecentCall.args[0]).toEqualXml(expected);
expect(someOtherService4Spy.method4).toHaveBeenCalledWith(jasmine.any(Function), jasmine.any(Function), [arg,arg]);
expect(someOtherService4Spy.method4).toHaveBeenCalledWith(jasmine.any(Function), jasmine.any(Function), [arg,arg]);
});
It is showing error on
expect(someOtherService3.method3.mostRecentCall.args[0]).toEqualXml(expected);
After debugging I found that it is not waiting for any promise to resolve, so method 1 return true, without even executing method3. I even tried with
someOtherService4Spy.method4.andReturn(function(){return deferred.promise;});
But result remain same.
My question is do I need to resolve multiple times ie for each promise. How can I wait till all the promises are executed.
method1 does not return the promise so how would you know the asynchrounous functions it calls are finished. Instead you should return:
return method2().then(
method6 calls asynchronous functions but again does not return a promise (it returns undefined) so how do you know it is finished? You should return:
return method1();
In a test you should mock $q and have it resolve or reject to a value but I can't think of a reason why you would have a asynchronous function that doesn't return anything since you won't know if it failed and when it's done.
Method 2 could be written in a more stable way because it would currently crash if the magically appearing myObject is empty (either {} or []
var method2 = function(){
var defer = $q.defer();
var keys = Object.keys(myObject);
return keys.reduce(
function(acc,item,index){
return acc.then(
function(){return method4(keys[index],myObject[key].data);},
function(err){console.log("error calling method4:",err,key,myObject[key]);}
)
}
,$q.defer().resolve()
)
};
And try not to have magically appearing variables in your function, this could be a global variable but your code does not show where it comes from and I doubt there is a need for this to be scoped outside your function(s) instead of passed to the function(s).
You can learn more about promises here you should understand why a function returns a promise (functions not block) and how the handlers are put on the queue. This would save you a lot of trouble in the future.
I did below modification to get it working. I was missing the handling of request to method5 due to which it was in hang state. Once I handled all the request to method 5 and provided successCallback (alongwith call to digest()), it started working.
someOtherService4Spy.responseArray = {};
someOtherService4Spy.requests = [];
someOtherService4Spy.Method4.andCallFake( function(successCallback, errorCallback, data){
var request = {data:data, successCallback: successCallback, errorCallback: errorCallback};
someOtherService4Spy.requests.push(request);
var error = function(errorCallback) {
request.errorCallback = errorCallback;
}
var success = function(successCallback) {
request.successCallback = successCallback;
return {error: error};
}
return { success: success, error: error};
});
someOtherService4Spy.flush = function() {
while(someOtherService4Spy.requests.length > 0) {
var cachedRequests = someOtherService4Spy.requests;
someOtherService4Spy.requests = [];
cachedRequests.forEach(function (request) {
if (someOtherService4Spy.responseArray[request.data[1]]) {
request.successCallback(someOtherService4Spy.responseArray[request.data[1]]);
} else {
request.errorCallback(undefined);
}
$rootScope.$digest();
});
}
}
Then I modified my test as :
it("test method6", function() {
myService.method6();
var expected = expected;
var dataDict = {data1:"data1", data2:"data2"};
for (var data in dataDict) {
if (dataDict.hasOwnProperty(data)) {
someOtherService4Spy.responseArray[dataDict[data]] = dataDict[data];
}
}
someOtherService4Spy.flush();
expect(someOtherService3.method3.mostRecentCall.args[0]).toEqualXml(expected);
expect(someOtherService4Spy.method4).toHaveBeenCalledWith(jasmine.any(Function), jasmine.any(Function), [arg,arg]);
});
This worked as per my expectation. I was thinking that issue due to chain of promises but when I handled the method5 callback method, it got resolved. I got the idea of flushing of requests as similar thing I was doing for http calls.
I'm creating a Node.js module with an asynchronous method - a simple HTTP GET request. Here is the code:
//mymodule.js
var https = require('https');
function getSomething(url_str)
{
var callback_fn = function(response){
var body = '';
response.on('data', function (data) {
body += data;
});
response.on('end', function () {
//console.log(body);
return body;
});
};
return https.request(url_str, callback_fn).end();
}
var module_obj = {
getSome: getSomething
};
module.exports = module_obj;
This module is called by my app.js - a web server - like so:
//app.js
var myModule = require('./mymodule');
var http = require('http');
var qs = require('querystring');
var server_fn = function(request, response){
response.setHeader('Access-Control-Allow-Origin', '*');
response.setHeader('Access-Control-Request-Method', '*');
response.setHeader('Access-Control-Allow-Methods', 'OPTIONS, GET');
response.setHeader('Access-Control-Allow-Headers', '*');
if ( request.method === 'OPTIONS' ) {
response.writeHead(200);
response.end();
return;
}
if (request.method == 'POST') {
var body = '';
request.on('data', function (data) {
body += data;
// Too much POST data, kill the connection!
// 1e6 === 1 * Math.pow(10, 6) === 1 * 1000000 ~~~ 1MB
if (body.length > 1e6)
request.connection.destroy();
});
request.on('end', function () {
var post = qs.parse(body),
post_url = post.url,
post_method = post.method;
var promise_flow = new Promise(function(resolve, reject){
if(post_method === 'get_me_something')
{
response_str = myModule.getSome(post_url);
resolve(response_str);
}
else
{
resolve('nothing');
}
});
promise_flow
.then(function(response){
response.write(response);
response.end();
return;
}).catch(function(error){
response.write(error);
response.end();
return;
})
});
}
};
var http_server = http.createServer(server_fn);
http_server.listen(2270);
console.log("server listening on 2270");
So basically, I start things up via node app.js, and then I post the URL, and then the module should fetch the Web page and then return the content.
Unfortunately, I'm getting the following error:
UnhandledPromiseRejectionWarning: Unhandled promise rejection (rejection id: 1): TypeError: First argument must be a string or Buffer
I believe this is because the response I'm getting from my modules getSomething method is false, as opposed to the content of the requested Web page.
I know I can fix this by moving the https.get operation from mymodule.js and putting it inline with app.js, and then calling resolve on end, but I'd like to keep the current module setup.
Is there a workaround to get the asynchronous method in the imported module to work with the existing promise chain as setup?
UPDATE
After further review, I noticed that I wasn't quite running things the right way. I updated the code as follows:
//...
var promise_flow = new Promise(function(resolve, reject){
if(post_method === 'get_me_something')
{
myModule.getSome(post_url)
.then(function(data){
resolve(data);
})
.catch(function(err){
reject(err);
});
}
else
{
resolve('nothing');
}
});
//...
This way, I think it fits with the true spirit of Promises.
Your getSomething function doesn't return a promise. Make it returns a promise, and fulfill the promise in response.on('end').
function getSomething(url_str)
{
return new Promise(function(resolve, reject) {
var callback_fn = function(response){
var body = '';
response.on('data', function (data) {
body += data;
});
response.on('end', function () {
//console.log(body);
resolve(body);
});
};
https.request(url_str, callback_fn).end();
});
}
Then in your main file, call it like this : myModule.getSomething(post_url).then(resolve);.
In my app i'm using restangular, and i have such method (which i converted from the plain $http request).
And i don't know how to use correctly restangular with promises. How is it possible?
Here is my code:
var test = function(){
var data = '{"Office":"' + office + '"}';
var deferred = $q.defer();
var person = Restangular.one('persons', id)
$scope.person.patch(data).then(function (response) {
deferred.resolve(response);
},function (err, status) {
deferred.reject(status);
});
return deferred.promise;
}
var runIt = function(){
test.then(...)...
}
You could simply use promise returned by patch method of Restangular.one instead of creating a new custom promise.
Code
var test = function() {
var data = '{"Office":"' + office + '"}';
var person = Restangular.one('persons', id);
//returned promise
return person.patch(data).then(function(response) {
return response.data;
}, function(err, status) {
return response.status;
});
}
test().then(...)
I have a function in my chartDirective which makes a call to a function in a service to GET data, format that data then call another function from my chartDirective:
function chartTicker(ticker, disabled) {
disabled = disabled || false;
var defer = $q.defer();
// Clear out previous chart:
d3.selectAll("svg > *").remove();
document.getElementById('chart').innerHTML = "";
chart = {},
chartData = [];
// Get and format data for chart:
document.getElementById('chart').innerHTML = "<svg></svg>";
var timeInHours = TimeSpanFactory.getTimeHours();
var promise = FormatChartDataFactory.getData(ticker, timeInHours).then(function() {
defer.resolve();
return defer.promise;
});
}
However I am getting the following error on the following line:
var promise = FormatChartDataFactory.getData(ticker, timeInHours).then(function() {
Here is my FormatChartDataFactory.getData function:
function getData(ticker, limit) {
var defer = $q.defer();
chartObj.chartData = [{}];
var limit_range = '';
if (limit > 0) {
limit_range = '?limit=' + limit;
}
getTickerPrice(ticker, limit_range).then(function() {
defer.resolve();
return defer.promise;
});
// GET Ticker data and return chartObj into drawChart:
////////////////////////////////////////////////////////////////////
function getTickerPrice(ticker, limit_range) {
return ApiFactory.getTickerQuotes(ticker.ticker, limit_range)
.success(function(data, status, headers, config) {
if (data.status === 'Success') {
// ....
Here is the link to my full FormatChartDataFactory gist file.
Promise should be return from code directly, it shouldn't be return from the .then callback of it. In short you have not returned promise from function and you are looking for .then method there, which results into an error.
Code
//1st place
var promise = FormatChartDataFactory.getData(ticker, timeInHours).then(function() {
defer.resolve();
});
return defer.promise;
//2nd place
getTickerPrice(ticker, limit_range).then(function() {
defer.resolve();
});
return defer.promise;
I am following this Angular tutorial on promises. I have a service that checks if an array is empty and if so, hits a REST service, returns a promise and updates the array. Here is the relative code:
requestingProviders: [],
getRequestingProviders: function() {
var that = this;
var deferred = $q.defer();
if(this.requestingProviders.length === 0) {
this.getResource().search({
role: 'REQUESTING_PROVIDER'
}, function(data) {
that.requestingProviders = data.providers;
deferred.resolve(data.providers);
});
return deferred.promise;
} else {
return that.requestingProviders;
}
}
The service is being called from a controller. Here is the code where it is being called:
$scope.providers = providerService.getRequestingProviders();
The REST call is made and returns fine, but the view is never updated. This is not working like the tutorial explained. Here is a plunker that shows what I am expecting. What am I doing wrong?
You need to resolve your promise:
var prom = providerService.getRequestingProviders();
prom.then(function (data) {
$scope.providers = data;
});
Also, change your code to always return the promise:
getRequestingProviders: function() {
var that = this;
var deferred = $q.defer();
if(this.requestingProviders.length === 0) {
this.getResource().search({
role: 'REQUESTING_PROVIDER'
}, function(data) {
that.requestingProviders = data.providers;
deferred.resolve(data.providers);
});
} else {
deferred.resolve(that.requestingProviders);
}
return deferred.promise;
}