Javascript consecutive promises not being fired - javascript

I am using PhantomJS to login a website using node.js. After that I want to make an HTTP request to obtain a json with data. My code is as follows:
phantom.create()
.then(function (ph) {
this.ph = ph;
return ph.createPage();
})
.then(function (page) {
this.page = page;
return page.open('https://example.com');
})
.then(function (status) {
if ( status === "success" ) {
return this.page.evaluate(function() {
document.querySelector("input[name='j_username']").value = 'example#example.net';
document.querySelector("input[name='j_password']").value = 'example';
console.log('Submitting logging...');
document.querySelector("input[name='submit']").click();
});
} else {
return null;
}
})
.then(function() {
console.log("Logged in!");
this.page.property('onResourceRequested', function(requestData, networkRequest) {
//This event is fired 40 times, only one is interesting to me the one that has the text maps.json in it
if (requestData.url.indexOf('maps.json') !== -1) {
return {headers: requestData.headers, url: requestData.url};
}
});
});
.then(function (data) {
//This then block is only fired once, with the first call of the first url in previous then block and data is null
if (data) {
// This code block is never fired because this then is only called once with data=null
console.log(data);
request.get({
uri: data.url,
headers: data.headers
}, function(err, res, body){
if (!err) {
callback(null, res.headers);
} else {
console.log("Error getting data from URL: "+ err);
callback(true, null);
}
});
}
});
There must be something wrong with this subsequent promises because the this.page.property('onResourceRequested'... function in the penultimate then block is fired like 40 times (one per each url called inside the website after login) but the last then is only fired once (when first url is requested).
I want to obtain the data from one concrete url (the one that contains maps.json in the url) which is call number 32.
What am I doing wrong? How can I fire the last then only when my call is done?
EDIT:
Following #charlieetfl's advice I canged the code to the following, but still not working...:
phantom.create()
.then(function (ph) {
this.ph = ph;
return ph.createPage();
})
.then(function (page) {
this.page = page;
return page.open('https://example.com');
})
.then(function (status) {
if ( status === "success" ) {
return this.page.evaluate(function() {
document.querySelector("input[name='j_username']").value = 'example#example.net';
document.querySelector("input[name='j_password']").value = 'example';
console.log('Submitting logging...');
document.querySelector("input[name='submit']").click();
});
} else {
return null;
}
})
.then(function() {
console.log("Logged in!");
return new Promise(function(resolve, reject) {
this.page.property('onResourceRequested', function(requestData, networkRequest) {
//This event is fired 40 times, only one is interesting to me the one that has the text maps.json in it
if (requestData.url.indexOf('maps.json') !== -1) {
resolve({headers: requestData.headers, url: requestData.url});
}
});
});
});
.then(function (data) {
//This then block is only fired once, with the first call of the first url in previous then block and data is null
if (data) {
// This code block is never fired because this then is only called once with data=null
console.log(data);
request.get({
uri: data.url,
headers: data.headers
}, function(err, res, body){
if (!err) {
callback(null, res.headers);
} else {
console.log("Error getting data from URL: "+ err);
callback(true, null);
}
});
}
});
EDIT: 21/07/2017 10:52 UTC.
I changed the code to this new one:
phantom.create().then(function(ph) {
ph.createPage().then(function(page) {
page.open('https://example.com/').then(function(status) {
if ( status === "success" ) {
page.evaluate(function() {
document.querySelector("input[name='j_username']").value = 'example';
document.querySelector("input[name='j_password']").value = 'example';
console.log('Submitting logging...');
document.querySelector("input[name='submit']").click();
}).then(function(){
page.property('onResourceRequested', function(requestData) {
if (requestData.url.indexOf('geomaps.json') !== -1) {
console.log(JSON.stringify(datos));
request.get({
uri: requestData.url,
headers: requestData.headers
}, function(err, res, body){
if (!err) {
console.log(res.headers);
} else {
console.log("Error getting data from website: "+ err);
}
});
}
});
});
}
});
});
});
Same result. The console.log(JSON.stringify(datos)); is being fired but the request.get is never being fired.
I thing may have something to do with firing async funcions inside promises?
EDIT 21/07/2017 11:09 UTC
More tests. If I simplify the page.property('onResourceRequested' code block I see that the then() is called only once and before the requestData is received for each call...
I am a little bit confused and I don't know right now how to approach this...

I finally resolved the issue.
page.property('onResourceRequested' executes in the PhantomJS process. PhantomJS does not share any memory or variables with node. So using closures in javascript to share any variables outside of the function is not possible.
using page.on('onResourceRequested' instead fixed the issue!

Related

How do I rewrite my AJAX that I will get a response in a particular order?

I'm making a cart in a e-commerce website.
This script delete one piece of product if user clicked on a button
$(".minusCart").click(function () {
var id = $(this).attr("data-id");
$.post("/cart/deleteOneAjax/" + id, {}, function (data) {
if (!data) {
location.reload();
} else {
$("#id-count" + id).val(data);
}
});
$.post("/cart/countAjax/" + id, {}, function (data) {
$("#cart-count").html(data);
});
$.post("/cart/productPriceAjax/" + id, {}, function (data) {
$("#product-price-cart" + id).html(data);
});
$.post("/cart/totalPriceAjax/" + id, {}, function (data) {
$(".total-price-cart").html(data);
});
});
So steps this script should do are:
Delete one piece of product
Calculate how many pieces are left
Calculate a sum for each product (quantity of product * price)
Calculate a sum for the whole cart
So the order is really important. But because those are asynchronous requests, responses could be sent back in a different order.
Because of that sometimes I get response for /cart/deleteOneAjax/ the last and all other data is not calculated properly.
What do I do in order to solve it?
ajax is asynchronous function, which mean the JS is not necessary to process your code from line 1 to line 100 according to the sequence that you expecting if there are async function in between.
In your case, the JS indeed process all your ajax request in the correct sequence, it just it isn't wait for cart/deleteOneAjax to response then only process the next ajax cart/countAjax, and so on.
How to solve?
The old way: Promise Then
The new way: async await
Here i demonstrate with Promise Then way:
function deleteOne(id) {
return new Promise((resolve, reject) => {
$.post("/cart/deleteOneAjax/" + id, {}, function (data) {
if (!data) {
reject("no data")
} else {
resolve(data)
}
});
})
}
function count(id) {
return new Promise((resolve, reject) => {
$.post("/cart/countAjax/" + id, {}, function (data) {
if (!data) {
reject("no data")
} else {
resolve(data)
}
});
})
}
function productPrice(id) {
return new Promise((resolve, reject) => {
$.post("/cart/productPriceAjax/" + id, {}, function (data) {
if (!data) {
reject("no data")
} else {
resolve(data)
}
});
})
}
function totalPrice(id) {
return new Promise((resolve, reject) => {
$.post("/cart/totalPriceAjax/" + id, {}, function (data) {
if (!data) {
reject("no data")
} else {
resolve(data)
}
});
})
}
$(".minusCart").click(function () {
var id = $(this).attr("data-id");
deleteOne(id).then(deleted => {
// your logic before process next ajax
count(id).then(countData => {
// your logic before process next ajax
productPrice(id).then(productPriceData => {
// your logic before process next ajax
totalPrice(id).then(totalPriceData => {
})
})
})
}).catch(deleteError=>{
//logic for deleteOne rejection scenario
})
})
One down side of Promise Then is callback hell, but that is for your future study.
Using ajax instead of using this post method will make your request handling much more customizable.
As a solution of this problem you can use 'await' keyword before your post function call, then you have to make the click event function 'async'.
And easy solution for now is to make this calls synchronous, replace the 'post' methods with 'ajax' calls.
$.ajax({
type: 'POST',
url: url,
data: data,
success: success,
dataType: dataType,
async:false //Here I am making it synchronus
});
See the official documentation here: https://api.jquery.com/jquery.ajax/

Wrap localForage setItem calls in Promise.all()

I want to refresh my indexeddb store with new data after a successful login. After the data refresh is complete, I want to redirect to the landing page. My problem is that I have 1000+ calls to setItem and they aren't finishing.
var app = {
Login: function () {
WebService.Login($("#username").val(), $("#password").val())
.then(function () {
// TODO: refresh data and then redirect...
UpdateData().then(function() {
window.location.href = '/Home';
});
})
.catch(function (err) {
console.log("error logging in");
});
},
UpdateData: function () {
return fetch('/api/Customer').then(function (response) {
return response.json();
})
.then(function (data) {
var customerStore = localforage.createInstance({ name: "customers" });
// Refresh data
customerStore.clear().then(function () {
data.forEach(function (c) {
// How do I know when all setItem calls are complete??
customerStore.setItem(String(c.CustomerID), c);
});
});
})
.catch(function (err) {
console.log("Data error", err);
});
}
}
I'm still relatively new to promises but there must be a way I can get all of the setItem calls into a Promise.all() that I can return. How can I do this?
I think that you need something like this:
return fetch("/api/Customer")
.then(function(response) {
return response.json();
})
.then(function(data) {
var customerStore = localforage.createInstance({ name: "customers" });
// Refresh data
return customerStore.clear().then(function() {
return Promise.all(
data.map(function(c) {
return customerStore.setItem(String(c.CustomerID), c);
})
);
});
})
.catch(function(err) {
console.log("Data error", err);
});
data.map will return an array of promises and then we also return the aggregate promise (from Promise.all).
You should also keep a reference of the customerStore for later use.
Also, if the amount of data is huge, you might want to use localForage-setItems to make the operation a bit more performant (but try to avoid a possible premature optimization).

Node Js express async response with request and Redis call

So i'm having an issue with handling async actions in NodeJS while trying to send response to a request, with some async calls in the middle. (And to make this party even more complicated, i'm also want to use async.parallel )
Basically i'm trying to get value from Redis, and if he doesn't exist, get it from a provider (with request and response based using axios).
This is the code snippet :
this.getFixturesByTimeFrame = function (timeFrame, res) {
function getGamesData(timeFrame,finalCallback) {
var calls = [];
var readyList = [];
//Creating calls for parallel
timeFrame.forEach(function(startDay){
calls.push(function(callback) {
//Problematic async call
redisClient.get(startDay, function (error, exist) {
console.log('Got into the redis get!');
if (error){
console.log('Redis error : '+error);
}
if (exist) {
console.log('Date is in the cache! return it');
return exist;
} else {
//We need to fetch the data from the provider
console.log('Date is not in the cache, get it from the provider');
getFixturesDataFromProvider(startDay)
.then(organizeByLeagues)
.then(function (gamesForADay) {
redisClient.setex(startDay, 600, gamesForADay);
responsesList.add(gamesForADay);
callback(null, gamesForADay);
}).catch(function (response) {
if (response.status == 404) {
callback('Cant get games from provider');
}
});
}
});
}
)});
async.parallel(calls, function(err, responsesList) {
/* this code will run after all calls finished the job or
when any of the calls passes an error */
if (err){
res.send(501);
} else {
console.log('Here is the final call, return the list here');
//Some data manipulation here - just some list ordering and for each loops
console.log('finished listing, send the list');
finalCallback(responsesList);
}
});
}
getGamesData(timeFrame, function (readyList) {
res.send(readyList);
});
};
function getFixturesDataFromProvider(date) {
var requestUrl = 'someURL/ + date;
return axios.get(requestUrl, config);
}
function organizeByLeagues(matchDay) {
if (matchDay.count == 0) {
console.log('No games in this day from the provider');
return [];
} else {
var organizedSet = [];
//Some list manipulations using for each
return organizedSet;
}
}
But the response is been sent before parallel has been starting doing his things...
i'm missing something with the callbacks and the async calls for sure but i'm not sure where...
Thanks

Returned unirest response in node.js is undefined

I am working on facebook bot, but I am in no way a node.js developer, this being my first time in using it, because I wanted to get out of my comfort zone for a little bit.
This is my request function
function requestExc() {
var resDictionary = {}
unirest.get("http://openapi.ro/api/exchange/" + queryDict["code"] + ".json")
.query({"date" : queryDict["date"]})
.end(function(res) {
if (res.error) {
console.log('GET error', res.error)
} else {
console.log('GET response', res.body)
resDictionary["rate"] = res.body["rate"]
resDictionary["date"] = res.body["date"]
}
})
console.log("resDictionary IS " + resDictionary)
///prints resDictionary IS [object Object]
return resDictionary
}
so I am trying to get it's result
var response = requestExc()
if (response !== null) {
respondToSender(response, sender)
}
and then act accordingly
function respondToSender(res, sender) {
console.log("RES IS " + res)
//prints RES IS [object Object]
if (res["rate"] === null) {
//do stuff
}
}
but when the variable gets to the respondToSender it's always undefined.
TypeError: Cannot read property 'rate' of undefined
I've also tried with Json.parse() but it's the same thing.
Ok, the problem is unirest (like many node.js modules) works asynchronously meaning that your code is quite probably executed in this order:
var response = requestExc() // request is sent first, ok
if (response !== null) { // this is done second
respondToSender(response, sender)
}
// the response arrived third, when it is not needed
So to deal with such stuff, you have to use callbacks/end method. See the example here:
unirest.post('http://mockbin.com/request')
.query('name=nijiko')
.query({
pet: 'spot'
})
.end(function (response) {
console.log(response); // this is where you should use respondToSender
});
The console.log(response); is launched only when reply came and that's what you want.
Someone from reddit taught me how to add a callback, and now it works as I want it. The complete code is:
// GET a resource
function requestExc(callback) {
unirest.get("http://openapi.ro/api/exchange/" + queryDict["code"] + ".json")
.query({"date" : queryDict["date"]})
.end(function(res) {
if (res.error) {
console.log('GET error', res.error)
callback(res.error, null)
} else {
console.log('GET response', res.body)
callback(null, res.body)
}
})
}
and I call it
var response = requestExc(function(error, res) {
console.log("date array is " + dateArray)
if (error === null) {
respondToSender(res["rate"], res["date"], sender, queryDict)
} else {
sendTextMessage(sender, "Imi pare rau, dar am intimpinat o problema in comunicarea cu BNR")
}
})
I wrap it in a Promise to get this to work, wondering if this is apt though and if it helps?
return new Promise((resolve, reject) => {
unirest('GET', 'https://url')
.headers({
'Accept': 'application/json'
})
.send("")
.then(function (response) {
if(!response.error) {
resolve(response)
} else {
reject(response)
}
})
});

can't submit a form on express/angular app

in my program there's a validation function on it, if there's an error it will prevent the form to submit and display error msg else it will console.log("Success") but my form cannot be submitted even without any error. is there anyway to enable status code 200 when there is no error ? because now the form prevent me to submit because of status code 400
express
function validateSignup(data,callback) {
"use strict";
var USER_RE = /^[a-zA-Z0-9_-]{2,25}$/;
var PASS_RE = /^.{6,100}$/;
var EMAIL_RE = /^[\S]+#[\S]+\.[\S]+$/;
if (!USER_RE.test(data.publicUsername)) {
callback(new Error('Invalid Public Username try just letters and numbers, e.g: Ed, 69, Kelvin and etc'), null);
}
if (!PASS_RE.test(data.password)) {
callback(new Error('Password must be at least 6 characters long'), null);
}
if (data.password != data.confirmPassword) {
callback(new Error('Password must match'), null);
}
if (!EMAIL_RE.test(data.email)) {
callback(new Error('Invalid email address'), null);
}
if (data.email != data.confirmEmail) {
callback(new Error('Email must match'), null);
}
return true;
}
handlesignup
this.handleSignup = function(req, res, next) {
"use strict";
validateSignup(req.body, function(error, data) {
if(error) {
res.send(400, error.message);
} else {
console.log("success");
}
})
}
Angular
function RegisterCtrl($scope, $http, $location) {
$scope.form = {};
$scope.errorMessage = '';
$scope.submitPost = function() {
$http.post('/register', $scope.form).
success(function(data) {
$location.path('/');
}).error(function(err) {
$scope.errorMessage = err;
});
};
}
You have multiple issues in your code.
Your validateSignup function doesn't always call its callback. If the input passes the validation, it shouldn't return true but instead call its callback with no error and the data:
function validateSignup(data,callback) {
// ...
callback(null, data);
}
You don't always answer the client's request:
validateSignup(req.body, function(error, data) {
if(error) {
res.send(400, error.message);
} else {
console.log("success");
res.send(200);
}
})
Edit: As a side note, a callback should aways be called asynchronously (ie. using process.setImmediate, process.nextTick or setTimeout), but that isn't an issue in your specific case as the callback will always be called synchronously. As noted in Effective JS, item 67:
Never call an asynchronous callback synchronously, even if the data is immediately available.
That's why my advice is to always call callbacks asynchronously, which will free you from weird bugs later on. There are a number of reasons as why you shouldn't do it, but the most obvious is that you can easily blow the stack.
Here's how you you can defer the callback execution:
function validateSignup(data,callback) {
// ...
process.setImmediate(function() {
callback(null, data);
});
}

Categories