I am a beginner in javascript, and I'm trying to figure out why my while loop won't actually loop more than once, even though the condition is always met.
I have a function sending an API request:
var get_status = function(trid, count) {
console.log(count);
var req = {
method: 'GET',
url: 'theUrlHere',
headers: {'headers'}
}
$http(req).success(function(data) {
if (data.transaction_status != 'Pending') {
// do something with the data
console.log('true');
return true;
}
else {
console.log('False');
return false;
}
}).error(function(data) {
// show an error popup
console.log('true');
return true;
})
}
};
I want to call this function until it returns true, so I call it this way:
var count = 0;
while (get_status(id, count) === false) {
count += 1;
}
The count variable is just added to see how many times it loops, it stays at 0 even though 'False' is displayed in the console.
Is there some behaviour I am misunderstanding here?
EDIT I understand why this won't work. My intention here is to display an iframe as long as the transaction status is pending. I thought of continually sending a request until the transaction status is something other then 'Pending', but I am aware there are more optimal ways.
Your get_status() function does not return a value. Thus, it's return value is undefined which is falsey so your while() loop stops after the very first iteration.
The return statements you do have in your code are inside of callbacks and have nothing to do with the return value of get_status().
What you are attempting to do is generally not a good design. It appears that you want to run a given Ajax call over and over with no delay until you get the answer you want. This will potentially hammer the destination server.
If you describe the problem you're really trying to solve, we could help come up with a better way to do this. Worst case, you could poll the server with a time delay between requests.
If you wanted to poll every so often, you could do something like this:
function get_status(trid, count) {
var req = {
method: 'GET',
url: 'theUrlHere',
headers: {'headers'}
}
return $http(req).then(function(data) {
return data.transaction_status;
});
}
function poll_status(callback) {
function next() {
get_status(...).then(function(status) {
if (status === "Pending") {
// poll once every two seconds
setTimeout(next, 2000);
} else {
// status is no longer pending, so call the callback and pass it the status
callback(status);
}
}, function(err) {
callback(err);
});
}
next();
}
poll_status(function(result) {
// done polling here, status no longer Pending
});
This is not the correct way to deals with async calls, I'd create a recursive function which will call itself. (in this case get_status should return a promise)
Code
var count = 0, id = 1;//id should be some value
(function myCall(promise){}
promise.then(function(data){
count += 1;
if(data)
myCall(get_status(id, count)); //call function on conditon
});
}(get_status(id, count))
Method(Returning Promise)
var get_status = function(trid, count) {
console.log(count);
var req = {
method: 'GET',
url: 'theUrlHere',
headers: {'headers'}
}
//returning promise here
return $http(req).then(function(response) {
var data = response.data;
if (data.transaction_status != 'Pending') {
// do something with the data
console.log('true');
return true; //resolves the promise
}
else {
console.log('False');
return false; //resolves the promise
}
}, function(data) {
// show an error popup
console.log('true');
return true;
})
}
};
You're trying to return from within an asynchronous callback, which won't work, unfortunately. Instead you'll want a module like async, specifically whilst.
var count = 0;
var outcome = false;
async.whilst(
function () { outcome = false; },
function (callback) {
count++;
// Your code here, setting outcome instead of returning
var req = {
method: 'GET',
url: 'theUrlHere',
headers: {'headers'}
}
$http(req).success(function(data) {
if (data.transaction_status != 'Pending') {
outcome = true;
callback();
}
else {
outcome = false
callback();
}
}).error(function(data) {
outcome = true;
callback();
})
},
function (err) {
// All done!
}
);
But really the behavior you're looking for is probably checking on a status at pre-defined intervals. In this case, adapting the code
var count = 0;
var outcome = false;
async.whilst(
function () { outcome = false; },
function (callback) {
count++;
// Your request stuff.
setTimeout(function () {
callback();
}, 1000); // Waits one second to begin next request
},
function (err) {
// All done!
}
);
Related
I'm running into an issue where my api call reaches it's timeout limit, but continues to loop for the remainder of the requests provided resulting in n number of timeout logs in the console (In this case 5). I want it so that I can do something along the lines of a break; and just exit entirely so the remaining calls don't get logged. E.g. If the call immediately times out, only one timeout log will be logged instead of the current 5 and none of the five api requests will be made.
let qs = {
requests: 5,
timeout: 1000
};
let prices = [];
let highest = 0;
const url = 'http://somedata.com';
function xhr(qs){
return $.ajax({
url: url,
dataType: 'json',
timeout: qs.timeout,
success: function (data) {
let json = JSON.stringify(data['price']);
prices.push(json);
getHighestPrice(prices);
console.log(highest);
},
error: function(e, textstatus, message) {
if(textstatus==="timeout") {
console.error(textstatus);
} else {
console.error(textstatus);
}
}
});
}
function makeRequest(qs) {
for(let i = 0; i < qs.requests; i++) {
xhr(qs);
}
}
function getHighestPrice(arr) {
for(let i = 0; i <= arr.length; i++) {
if (arr[i] > highest) {
highest = arr[i]
}
}
return highest;
}
makeRequest(qs);
Your code makes all the requests at once
It should be noted that this code will stop the "chaining" once any error occurs in $.ajax, not just timeout - if that's not the required behaviour, there is a little more to do
To make the call only if the previous is successful, you can chain the promises returned by $.ajax
let qs = {
requests: 5,
timeout: 1000
};
let prices = [];
let highest = 0;
function xhr(qs){
return $.ajax({
url: url,
dataType: 'json',
timeout: qs.timeout,
success: function (data) {
let json = JSON.stringify(data['price']);
prices.push(json);
getHighestPrice(prices);
console.log(highest);
},
error: function(e, textstatus, message) {
if (textstatus==="timeout") {
console.error(textstatus);
} else {
console.error(textstatus);
}
}
});
}
function makeRequest(qs) {
let p = $.when();
for(let i = 0; i < qs.requests; i++) {
p = p.then(() => xhr(qs));
}
}
as others have pointed out you don't need to pass qs to xhr, however, I'm assuming the code you posted may be simplified so have not removed the qs argument
An alternative would be
let qs = {
requests: 5,
timeout: 1000
};
let prices = [];
let highest = 0;
function xhr(qs){
return $.ajax({
url: url,
dataType: 'json',
timeout: qs.timeout
}).then(data => {
let json = JSON.stringify(data['price']);
prices.push(json);
getHighestPrice(prices);
console.log(highest);
});
}
function makeRequest(qs) {
let p = $.when([]);
for(let i = 0; i < qs.requests; i++) {
p = p.then(() => xhr(qs));
// or p.then(xhr); if you don't need to pass qs on to xhr function (remove qs argument in xhr as well)
}
p.then(() => {
// this is run once all have completed
}).fail(reason => {
// this is run if there's a failure anywhere
});
}
Since it is a callback, it will be executed asynchronously. So even if you throw an error from one of the callback you provided, the rest will be executed later or sooner. One of the solution I could think of is to have a flag that will be set to true if one of the AJAX causes an error. Something like:
var hasError = false;
$.ajax({
error: function (e, textstatus, message) {
if (textstatus === "timeout") {
if (!hasError) console.error(textstatus);
hasError = true;
}
}
});
Using Promise.all() can simplify this use case. If you cannot use promises try throwing an exception from the error handler. Like so:
$.ajax({
error: function (e, textstatus, message) {
if (textstatus === "timeout") throw e
}
})
Be sure to catch the the exception:
function makeRequest(qs) {
try {
for(let i = 0; i < qs.requests; i++) {
xhr(qs);
}
} catch (e) { // Handle error here }
}
To get the desired behavior you will have to make all of the calls sequentially i.e. you can't start the next call until the previous one has finished (otherwise you won't know if it has failed or not).
You could use the done callback to determine whether the next call should be made:
function makeRequest(i) {
xhr().done(function(){
if (i < qs.requests){
makeRequest(i+1)
}
})
}
makeRequest(0); // Kick things off here
Also, you don't need to pass the qs variable into the makeRequest or xhr functions. It doesn't change throughout the calls so just use it as-is within the xhr function without passing it around.
I've a bunch of functions which are nested due to top level function is a ajax request.
So i want to return a value instead of a promise in nested child function.
Parent
let getUserPermissions = function(id) {
let deferred = $q.defer();
let promise = accessRequestService.getPermissions(id);
promise.then(function(data) {
deferred.resolve(data);
}, function(err) {
deferred.reject(err);
})
return deferred.promise;
}
Child 1
$rootScope.userInit = function() {
return getUserPermissions(vzid)
.then(function(data) {
//Some code here
return data;
})
}
Child 2
let checkAuthorize = function(toState) {
return $rootScope.userInit().then(
function(data) {
//some code here
return data;
});
}
Level 3
checkAuthorize(toState).then( function(val){
$rootScope.isAuthorized = val;
if ($rootScope.isAuthorized == true) {
$log.info('is Authorized')
} else {
$log.info('is not Authorized');
throw new AuthorizationError()
}
})
At Level 3 we are still working with a promise. Can child 2 return a value instead of promise.
Expectation # Level 3
$rootScope.isAuthorized = checkAuthorize(toState);
if ($rootScope.isAuthorized == true) {
$log.info('is Authorized')
} else {
$log.info('is not Authorized');
throw new AuthorizationError()
}
The hard truth is: you can't, unless you want spaghetti code all around.
The best solution would be to use something like ui-router's resolve, getting all the permissions needed before the page is shown to the user. Then, you could use them on your controllers without any asynchronous calls.
You can use for it async/await construction. And use Babel for support old browsers.
Async
Await
function resolveAfter2Seconds(x) {
return new Promise(resolve => {
setTimeout(() => {
resolve(x);
}, 2000);
});
}
async function f1() {
var x = await resolveAfter2Seconds(10);
console.log(x); // 10
console.log('done');
}
f1();
Yes, this type of thing is possible, but it will change the behavior. You'll probably want to keep userInit, but you also add a userInitValue variable and initialize it as follows:
let userInitValue = null;
let userInit = function() {
return getUserPermissions()
.then(function(data) {
userInitValue = data;
return data;
})
}
So now userInitValue will start as null and then later be initialized to the relevant data.
function isKnownAuthorized(toDoSomething) {
// If we don't know whether the user is authorized
// because we are still waiting for the server to tell us
// then return false and disallow access for now
if(!userInitValue) return false;
// Otherwise return the truth
// (as of when we got the server response)
return userInitValue.isAuthorized(toDoSomething);
}
Note again the change in behavior. The price of getting an instant response, perhaps before the server gives you the data, is that the instant response could be wrong. So don't use this in a one-time :: expression in AngularJs.
Based on what you're hoping to achieve in Level 3, I'm guessing this function is going to be called multiple times with the same input. In this case, what I would do is make the call to the promise if there is not a cached result, and cache the result. This way you don't have to go down the promise chain, although I only count one promise in the code provided. There are multiple handlers on resolve, but only one promise.
You can run your code as if it was synchronous using nsynjs: it will evaluate code step-by-step, and if some function returns promise, it will pause execution, wait until promise is resolved, and assigns resolve result to data property. So, code below will be paused on level 1 until promise is resolved to actual value.
var getUserPermissions = function(id) {
return new Promise(function(resolve, reject) {
setTimeout(function(){
resolve({
id: id,
isAdmin: "yes he is",
})
}, 1000);
});
};
function synchronousCode() {
console.log("start");
var vzid = 35;
var userInit = function() {
return getUserPermissions(vzid).data;
};
var checkAuthorize = function() {
return userInit().isAdmin;
};
var isAuthorized = checkAuthorize();
console.log(isAuthorized);
};
nsynjs.run(synchronousCode, null, function(){
console.log("finish");
});
<script src="https://rawgit.com/amaksr/nsynjs/master/nsynjs.js"></script>
I'm using $state.transitionTo Method to be called before $stateChangeStart.
var transitionTo = $state.transitionTo;
$state.transitionTo = function(to, toParams, options) {
var from = $state.$current,
fromParams = $state.params;
to = to.name ? to : $state.get(to);
$rootScope.state = {
to: to.self,
toParams: toParams,
from: from.self,
fromParams: fromParams,
options: options
}
if (options.notify && options.notify !== false) {
return $q.reject(new AuthorizationError('Rejecting $state.transitionTo', 'Transition Rejected'));
} else {
return checkAuthorize(to).then(function(auth) {
$rootScope.isAuthorized = auth;
return transitionTo(to, toParams, options)
})
}
}
StateChangeStart
$rootScope.$on('$stateChangeStart', function(event, toState, toParams, fromState, fromParams) {
$log.info("Route change start from", fromState.url, "to", toState.url);
//event.preventDefault();
if ($rootScope.isAuthorized == true) {
$log.info('is Authorized')
//$state.go($rootScope.toState.name);
} else {
event.preventDefault();
$log.info('is not Authorized');
throw new AuthorizationError('User is not Authorized.', 'NOT_AUTHENTICATED')
}
});
I'm basically just trying to verify if a resource is reachable from the executing client. I can not use XHR, because the target resource doesn't allow that.
I'm pretty new to JS and am currently working with this ( executable here ):
var done = false;
var i = 1;
var t = "https://i.stack.imgur.com/Ya15i.jpg";
while(!done && i < 4)
{
console.log("try "+i);
done = chk(t);
sleep(1000);
i = i+1;
if (done)
{
console.log("Reachable!");
break;
}
else
{
console.log("Unreachable.");
}
}
function chk(target)
{
console.log("checking "+target)
fetch(target, {mode: 'no-cors'}).then(r=>{
return true;
})
.catch(e=>{
return false;
});
}
// busy fake sleep
function sleep(s)
{
var now = new Date().getTime();
while(new Date().getTime() < now + s){ /* busy sleep */ }
}
I was expecting this code to check for the resource, print the result, then wait for a sec. Repeat this until 3 tries were unsuccessful or one of them was successful.
Instead the execution blocks for a while, then prints all of the console.logs at once and the resource is never reachable (which it is).
I do know that the fetch operation is asynchronous, but I figured if I previously declare done and implement a sleep it should work. In the worst case, the while loop would use the previously declared done.
How do I achieve the described behavior? Any advice is welcome.
Your sleep function is blocking, what you really want is a recursive function that returns a promise after checking the url n times with a delay of y seconds etc.
Something like this
function chk(target, times, delay) {
return new Promise((res, rej) => { // return a promise
(function rec(i) { // recursive IIFE
fetch(target, {mode: 'no-cors'}).then((r) => { // fetch the resourse
res(r); // resolve promise if success
}).catch( err => {
if (times === 0) // if number of tries reached
return rej(err); // don't try again
setTimeout(() => rec(--times), delay ) // otherwise, wait and try
}); // again until no more tries
})(times);
});
}
To be used like this
var t = "https://i.stack.imgur.com/Ya15i.jpg";
chk(t, 3, 1000).then( image => {
console.log('success')
}).catch( err => {
console.log('error')
});
And note that this does not fail on 404 or 500, any response is a successful request.
The main problem is that you are trying to return from callback. That makes no sense.
But fetch is Promise based request you can use Promise to simulate delays as well
Something like this should do the trick
// promise based delay
const delay = timeout => new Promise(resolve => setTimeout(resolve, timeout))
// check if target can be fetched
const check = target => fetch(target, {...})
.then(response => response.ok)
const ping = (target, times = 3, timeout = 1000) => check(target)
.then(found => {
if(!found && times) { // still can check
// wait then ping one more time
return delay(timeout).then(() => ping(target, times - 1, timeout))
}
return found
})
ping('https://i.stack.imgur.com/Ya15i.jpg')
.then(found => {
console.log(found ? 'Reachable': 'Unreachable')
})
Your chk function returns undefined, you return true/false from promise callbacks not from container function.
You should use recursion and timeout in catch callback.
It will be something like this:
var i = 0;
var done = false;
var t = "https://i.stack.imgur.com/Ya15i.jpg";
(function chk(target){
console.log("checking "+target)
fetch(target, {mode: 'no-cors'}).then(r=>{
done = true;
console.log("Reachable!");
})
.catch(e=>{
console.log("Unreachable.");
if(i<4){
setTimeout(function(){
chk(target)
},1000)
}
});
})(t)
You can't return within a callback. When you do, it is the callback that is returning, not the parent function. If fact, the function chk is never returning anything.
What it sounds like you are intending to do is return the promise returned by fetch. And attempt to fetch three times.
Try this:
const numberOfTries =3;
currentTry = 1;
var t = "https://i.stack.imgur.com/Ya15i.jpg";
chk(t);
function tryCheck(resource, currentTry) {
chk(resource).done(function(){
console.log("Reachable!");
}).catch(function(e) {
console.log("Unreachable.");
if (currentTry >= numberOfTries) return;
sleep(1000);
tryCheck(resource, currentTry + 1);
});
}
function chk(resource) {
console.log("checking "+target);
return fetch(target, {mode: 'no-cors'});
}
Try this, Hope it works
var myHeaders = new Headers();
myHeaders.append('Content-Type', 'image/jpeg');
var myInit = { method: 'GET',
headers: myHeaders,
mode: 'no-cors',
cache: 'default' };
var myRequest = new Request('https://i.stack.imgur.com/Ya15i.jpg');
fetch(myRequest,myInit).then(function(response) {
...
});
I am trying to reuse the the data returned from promise here. But, the problem is, after the first call to checkPromise function, it immediately calls the second function, and the promise for the first function is not fulfilled, so it never returns any data, and hence it never enters the if clause. How do I reuse a promise?
var Promise = require('bluebird');
var request = Promise.promisify(require("request"));
var url = 'http://www.google.com';
var obj = new Object;
function apiCall(url) {
return new Promise(function (resolve, reject) {
request(url).spread(function(response, body) {
return resolve(body);
}).catch(function(err) {
console.error(err);
return reject(err);
});
});
}
function checkPromise(url) {
if(obj.hasOwnProperty(url)) {
var rp = obj[url];
//do something
}
else {
apiCall(url).then(function(result) {
obj[url] = result;
//do something
});
}
}
checkPromise(url);
checkPromise(url);
You likely have a timing issue. Your apiCall() function is asynchronous. That means it finishes sometime later. As such, each time you call checkPromise(), all you're doing is starting a request and it finishes sometime later. So, you call it the first time and it starts a request (that has not finished yet). Then, your next call to checkPromise() gets called and it does it's if check before the first call has completed. Thus, it finds nothing in the cache yet.
Your code is running two requests in parallel, not one after the other.
If you actually want to wait until the first request is done before executing the second one, then you will have to actually structure your code to do that. You would need to make checkPromise() return a promise itself so code using it could known when it was actually done in order to execute something after it was done.
FYI, I don't see anything in your code that is actually related to reusing promises (which is something you cannot do because they are one-shot objects).
Here's one possible implementation:
var Promise = require('bluebird');
var request = Promise.promisify(require("request"));
var url = 'http://www.google.com';
var obj = {};
function apiCall(url) {
return request(url).spread(function(response, body) {
return body;
});
}
function checkPromise(url) {
if(obj.hasOwnProperty(url)) {
var rp = obj[url];
//do something
return Promise.resolve(rp);
}
else {
return apiCall(url).then(function(result) {
obj[url] = result;
//do something
return result;
});
}
}
checkPromise(url).then(function() {
checkPromise(url);
});
Significant changes:
Return the promise returned by request() rather than create yet another one.
Change checkPromise() so it always returns a promise whether the value is found in the cache or not so calling code can always work consistently.
Sequence the two checkPromise() calls so the first can finish before the second is executed.
A very different approach would be to actually wait on the cache if a result you are interested in is already being loaded. That could be done like this:
var Promise = require('bluebird');
var request = Promise.promisify(require("request"));
var url = 'http://www.google.com';
var obj = {};
function apiCall(url) {
return request(url).spread(function(response, body) {
return body;
});
}
function checkPromise(url) {
if(obj.hasOwnProperty(url)) {
// If it's a promise object in the cache, then loading
// If it's a value, then the value is already available
// Either way, we wrap it in a promise and return that
return Promise.resolve(obj[url]);
} else {
var p = apiCall(url).then(function(result) {
obj[url] = result;
//do something
return result;
});
obj[url] = p;
return p;
}
}
checkPromise(url).then(function(result) {
// use result
});
checkPromise(url).then(function(result) {
// use result
});
few problems with your code, first in apiCall, you are doing a promise ant-pattern( no need for that new promise), second your checkPromise is doing a sync operation, so it must either return a promise or have a callback argument, so you code can be changed into:
var Promise = require('bluebird');
var request = Promise.promisify(require("request"));
var url = 'http://www.google.com';
var obj = new Object;
function apiCall(url) {
return request(url).spread(function(response, body) {
return body;
}).catch(function(err) {
console.error(err);
throw err;
});
}
function checkPromise(url) {
var promise = Promise.resolve();
if(obj.hasOwnProperty(url)) {
var rp = obj[url];
//do something
}
else {
return apiCall(url).then(function(result) {
obj[url] = result;
//do something
});
}
return promise;
}
checkPromise(url).then(function(){
return checkPromise(url);
});
Given the way you are globally storing the result in 'obj[url]', it'd probably be easiest to do
function checkPromise(url) {
if (!obj[url]) obj[url] = apiCall(url);
obj[url].then(function(result) {
//do something
});
}
to basically make the request, if it hasn't already started, then attach a listener to the promise for when the result has loaded.
Here is the simplest example of how to prevent multiple API calls if there are multiple similar request for something (cache check for example)
var _cache = {
state: 0,
result: undefined,
getData: function(){
log('state: ' + this.state);
if(this.state === 0 ){ // not started
this.state = 1; // pending
this.promise = new Promise(function(resolve, reject) {
return (apiCall().then(data => { _cache.result = data; _cache.state = 2; resolve(_cache.result) }));
})
return this.promise;
}
else if(this.state === 1){ // pending
return this.promise;
}
else if(this.state === 2){// resolved
return Promise.resolve(this.result);
}
},
};
Simulating api call
function apiCall(){
return new Promise(function(resolve, reject) {
log('in promise')
setTimeout(() => {
log('promise resolving')
resolve(1);
}, 1000);
})
}
Making simultaneous requests.
_cache.getData().then(result => { log('first call outer: ' + result);
_cache.getData().then(result => { log('first call inner: ' + result); });
});
_cache.getData().then(result => { log('second call outer: ' + result);
_cache.getData().then(result => { log('second call inner: ' + result); });
});
Only one API call is maden. All others will wait for completion or use the resolved result if it already completed.
In my controller with the help of service i'm sending some qr-data to users so:
$scope.sendtoList = function () {
$scope.qrStatus = false;
angular.forEach($scope.users, function (item) {
if (item.Selected){
inviteService.sendQR(item.Emails.main, $scope.company.Id).then(function(response) {
$scope.qrStatus = true;
},
function(err) {
$scope.qrStatus = false;
});
}
});
if ($scope.qrStatus){
$window.alert('QR-code has been sended successfully.');
}
else{
$window.alert('Warning! QR-code has not been sended successfully.');
}
}
and i see some strange behaviour: it always show warning alert, even if method is done succesfully - i think it is of promisses. But how could i show window in my case only after servise promisse is returned?
In order to do this with promises, you need to create a counter that checks every time a user is updated, and then triggers the alert once all are accounted for.
$scope.sendQRtoList = function () {
$scope.qrStatus = false;
var count = 0;
var length = $scope.users.length;
var error = 0;
angular.forEach($scope.users, function (item) {
if (item.Selected){
inviteService
.sendQR(item.Emails.main, $scope.company.Id)
.then(function(response) {
//yay!
},
function(err) {
error++;
})
.finally(function () {
count++;
if (count === length && error === 0) {
$window.alert('QR-code has been sent successfully.');
}
if (count === length && error !== 0) {
$window.alert('Warning! QR-code has not been sent successfully.')
}
});
}
});
};
.finally() happens on every promise, that's where you want to add your counter incrementation.
Hope one of the following helps
1) Try looking at the following post: $success call back function from AngularJS
Its a similar question and the following js fiddle might help: http://jsfiddle.net/E5HGy/6/
2) A counter as stated above would also solve the problem, something such as
if(i == selector.length)
// "callback"
would essientially solve it