Problem: Extracting data from ajax request inside page.evaluate
Description: I usually get variables out of page.evaluate by simply returning them. However, I need to make an ajax request within the context of a page, and then I need to process its result out of the page's context.
The code I'm trying to fix is:
var theOutput = page.evaluate(function () {
return $.ajax({
async: false,
url: 'http://localhost:8080/captcha.php',
data: { filename: 'C:\\wamp\\www\\images\\0.png' },
type: 'post',
success: function (output) {
parsed_output = $.parseHTML(output);
return parsed_output[4].data.trim();
},
});
});
console.log(theOutput);
The variable parsed_output[4].data.trim() is a string. But when I log output I get a [object Object], with the properties abort, always, complete, done, error, fail, getAllResponseHeaders, getResponseHeader, overrideMimeType, pipe null, progress, promise, readyState, setRequestHeader, state, statusCode, success,then.
Question: How can I extract theOutput from page.evaluate?
Since this is a blocking AJAX request, you can create a temporary variable:
var theOutput = page.evaluate(function () {
var result;
$.ajax({
async: false,
...
success: function (output) {
parsed_output = $.parseHTML(output);
result = parsed_output[4].data.trim();
},
});
return result;
});
console.log(theOutput);
You can also directly access the responseText from the jqXHR object:
var theOutput = page.evaluate(function () {
var jqXHR = $.ajax({
async: false,
url: 'http://localhost:8080/captcha.php',
data: { filename: 'C:\\wamp\\www\\images\\0.png' },
type: 'post'
});
parsed_output = $.parseHTML(jqXHR.responseText);
return parsed_output[4].data.trim();
});
console.log(theOutput);
If you fear that async: false is deprecated, you can simply use the underlying XMLHttpRequest to use blocking execution:
var theOutput = page.evaluate(function () {
var request = new XMLHttpRequest();
request.open('POST', 'http://localhost:8080/captcha.php', false);
request.send($.param({ filename: 'C:\\wamp\\www\\images\\0.png' }));
var parsed_output = $.parseHTML(request.responseText);
return parsed_output[4].data.trim();
});
console.log(theOutput);
Related
i'm trying to get my head around working with Deffered objects especially in cases where you have to perform multiple asynchronous operations on every item in an array. In the code below i just want to be able to access the result of an asynchronous array after it is complete, in my case it is the results array.
To explain the code below
ListData function derives the source data which is an array that i intend to manipulate.
getPictureComplete1 perfroms an async operation (ListDataWithPicture) on every item in the array above
The idea of step 2 was to add an image url to every item in the array from Step 1 and then use the new array as an input to step 4
could be to print the images to the page or perform additional manipulations on the array
var mydeferred = $.Deferred();
var ListData = function (){
listName = 'TeamInfo';
$.ajax({
url: _spPageContextInfo.webAbsoluteUrl + "/_api/web/lists/getbytitle('"+listName+"')/items?$select=Name/Title,Name/Name,Name/Id,Name/EMail,Name/WorkPhone&$expand=Name/Id",
type: "GET",
headers: { "ACCEPT": "application/json;odata=verbose" },
success: onQuerySucceded,
error: onQueryFailed
});
return mydeferred.promise();
}
var ListDataWithPicture = function(userId, callback) {
// execute AJAX request
$.ajax({
url: _spPageContextInfo.siteAbsoluteUrl + "/_api/web/SiteUserInfoList/items?$filter=Id eq " + userId + "&$select=Picture",
type: "GET",
async: false,
headers: { "ACCEPT": "application/json;odata=verbose" },
success: function(data){
console.log("Starting async operation for " + userId);
var pictureLink = "";
var mydata = callback(data.d.results[0].Picture.Url);
return mydata
},
error: onQueryFailed
});
}
function onQuerySucceded (data){
var PeopleCompleteList = [];
for (i=0; i< data.d.results.length; i++) {
//check if the user exists if he does store the following properties name,title,workphone,email
if(data.d.results[i]['Name'] != null){
personName = data.d.results[i]['Name'].Name.split('|')[2];
userName = data.d.results[i]['Name']['Name'];
UserTitle = data.d.results[i]['Name']['Title'];
UserphoneNumber = data.d.results[i]['Name']['WorkPhone'];
UserEmail = data.d.results[i]['Name']['EMail'];
Id = data.d.results[i]['Name']['Id'];
PeopleCompleteList.push(PersonConstructor(personName, UserTitle, UserphoneNumber,UserEmail,Id));
}
}
mydeferred.resolve(PeopleCompleteList);
}
function getPictureComplete1 (data){
var def = new $.Deferred();
var results = [];
var expecting = data.length;
data.forEach(function(entry, index) {
//this is the asynchronous function
ListDataWithPicture(entry.UserId, function(result) {
results[index] = {imageUrl: result, UserId: entry.UserId, name: entry.name, Title: entry.Title, phoneNumber: entry.phoneNumber, Email: entry.Email};
//console.log(result);
if (--expecting === 0) {
// Done!
console.log("Results:", results); //this works succeffully from here
def.resolve();
return results
//mydeferred.resolve(results);
}
});
});
return mydeferred.promise();
}
$(function () {
ListData().then(function(data){
//how can i access the results array in this function after it has completed??
var value = getPictureComplete1 (data);
//the line below results undefined,which i understand because the getPictureComplete1 function may not have completed at the time
console.log(value);
});
Because this is an asynchronous operation, the only time that you have access to the array of results from your ajax call in LineData is within the scope of the onQuerySucceded function that you assigned to the success property in your ajax configuration. The function you define here will be fired after a successful response was received.
When establishing a deferred promise, you will need to define what data resolves the promise. Whatever you pass into the resolve method will be available as a parameter within a subsequent then block on your promise chain.
I'm not seeing onQuerySucceded defined in your example, but it would look something like this:
var ListData = function (){
var mydeferred = $.Deferred();
listName = 'TeamInfo';
$.ajax({
url: _spPageContextInfo.webAbsoluteUrl + "/_api/web/lists/getbytitle('"+listName+"')/items?$select=Name/Title,Name/Name,Name/Id,Name/EMail,Name/WorkPhone&$expand=Name/Id",
type: "GET",
headers: { "ACCEPT": "application/json;odata=verbose" },
success: function onQuerySuccess(data) {
mydeferred.resolve(data);
},
error: onQueryFailed
});
return mydeferred.promise();
}
Now, after a successful call, the promise will resolve with the data. So, something like this is possible:
ListData()
.then(function (data) {
// do something with the data
})
Similarly, you will want to define what are the rejection cases for your deferred promise. For example, maybe the ajax call doesn't succeed. For this, you will want to use the reject method on the deferred object.
For example:
var ListData = function (){
var mydeferred = $.Deferred();
listName = 'TeamInfo';
$.ajax({
url: _spPageContextInfo.webAbsoluteUrl + "/_api/web/lists/getbytitle('"+listName+"')/items?$select=Name/Title,Name/Name,Name/Id,Name/EMail,Name/WorkPhone&$expand=Name/Id",
type: "GET",
headers: { "ACCEPT": "application/json;odata=verbose" },
success: function onQuerySuccess(data) {
mydeferred.resolve(data);
},
error: function onQueryFailed(error) {
mydeferred.reject(error);
}
});
return mydeferred.promise();
}
This works similarly to resolve, but will resolve to any subsequent catch or fail blocks. So, something like this will work
LineData()
.then(function (data) {
// it was successful, do something with the data
})
.catch(function (error) {
// There was an error. The then block was not called.
// Do something with the error.
})
Put even more simply, you can set mydeferred.resolve and mydeferred.reject as these properties directly. Like so:
var ListData = function (){
var mydeferred = $.Deferred();
listName = 'TeamInfo';
$.ajax({
url: _spPageContextInfo.webAbsoluteUrl + "/_api/web/lists/getbytitle('"+listName+"')/items?$select=Name/Title,Name/Name,Name/Id,Name/EMail,Name/WorkPhone&$expand=Name/Id",
type: "GET",
headers: { "ACCEPT": "application/json;odata=verbose" },
success: mydeferred.resolve,
error: mydeferred.reject
});
return mydeferred.promise();
}
You will want to do something similar with your ListDataWithPicture function so that it also returns a promise with the data that you need:
var ListDataWithPicture = function(userId, callback) {
var deferred = $.Deferred();
$.ajax({
url: _spPageContextInfo.siteAbsoluteUrl + "/_api/web/SiteUserInfoList/items?$filter=Id eq " + userId + "&$select=Picture",
type: "GET",
async: false,
headers: { "ACCEPT": "application/json;odata=verbose" },
success: function(data){
deferred.resolve(data.d.results[0].Picture.Url)
},
error: deferred.reject
});
return deferred.promise();
}
This allows you to do something like this:
LineData()
.then(function (data) {
// performing on just the first result:
return ListDataWithPicture(data[0]);
})
.then(function (url) {
// do something with the result
})
.catch(function (error) {
// do something with the error
});
Because you want to perform a aynchronous operation on each of the items in your array, I'd recommend to use Promise.all which executes and resolves an array of promises.
LineData()
.then(function (data) {
// create a map of promises
var promises = data.map(function (item) {
return ListDataWithPicture(item);
});
return Promise.all(promises);
})
.then(function (urls) {
// urls will be an array of urls resolved from calling
// ListDataWithPicture on each item in the array resolved
// from above
})
.catch(function (error) {
// do something with the error
});
Here are some good resources to learn more:
http://api.jquery.com/category/deferred-object/
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/all
This question already has answers here:
Why is my variable unaltered after I modify it inside of a function? - Asynchronous code reference
(7 answers)
Closed 7 years ago.
I call a function which registers a registrationId is issued using chrome.gcm. Everything is fine but because the callback takes time, my code does not work without a console.log or alert. Any tips how I can make it wait?
var registrationId = ""
function register() {
var senderId = 'MY_SENDER_ID';
chrome.gcm.register([senderId], registerCallback);
}
function registerCallback(regId) {
registrationId = regId;
if (chrome.runtime.lastError) {
return false;
}
chrome.storage.local.set({registered: true});
}
$(function(){
$("#register-form").submit(function(e) {
//Disable from further calls
$('#submit').disabled = true;
register()
var name = $('#name').val()
var email = $('#email').val()
//Insert console.log or alert here to slow it down
var chromeId = registrationId
$.ajax({
type: "POST",
url: 'MY_URL',
ajax:false,
data: {chromeId: chromeId, name: name, email:email},
success: function(result)
{
console.log(result)
}
});
});
})
You need to execute the method as part of the callback, since the value that needs to be passed in as part of your AJAX request, is available only after ASYNC process completes.
You can use a Deferred objects in such cases. As soon as the it is resolved you can execute your AJAX call.
$(function() {
$("#register-form").submit(function(e) {
//Disable from further calls
$('#submit').disabled = true;
var senderId = 'MY_SENDER_ID';
// Store the promise in a variable
var complete = chrome.gcm.register([senderId]);
// When resolved it will, hit the callback
// where you have access to the value
// which is then passed to your AJAX request
$.when(complete).done(function(regId) {
var registrationId = regId;
if (chrome.runtime.lastError) {
return false;
}
chrome.storage.local.set({
registered: true
});
$.ajax({
type: "POST",
url: 'MY_URL',
ajax: false,
data: {
chromeId: registrationId,
name: name,
email: email
},
success: function(result) {
console.log(result)
}
});
});
});
});
The code that comes after register() should go in a new callback which accepts registrationId as a parameter, and is passed to register(). Then, register() can call this callback with the registrationId it gets back from chrome.gcm.register. No need for the global registrationId variable.
function register(callback) {
var senderId = 'MY_SENDER_ID';
chrome.gcm.register([senderId], function (regId) {
if (chrome.runtime.lastError) {
return false;
}
chrome.storage.local.set({registered: true});
callback(regId);
});
}
$(function(){
$("#register-form").submit(function(e) {
//Disable from further calls
$('#submit').disabled = true;
register(function (registrationId) {
var name = $('#name').val()
var email = $('#email').val()
//Insert console.log or alert here to slow it down
var chromeId = registrationId
$.ajax({
type: "POST",
url: 'MY_URL',
ajax:false,
data: {chromeId: chromeId, name: name, email:email},
success: function(result)
{
console.log(result)
}
});
});
});
})
Promises and async/await helps with stuff like this, also.
This question already has answers here:
How do I return the response from an asynchronous call?
(41 answers)
Closed 9 years ago.
I want to build a javascript class which I initialize and will make only one ajax request to save some data off so I can do stuff with it aftewards. It is important that there is only one request for performance reasons.
Here is the initialisation
var web = new webService();
web.connect(app.info);
web.info();
and this is my class
function webService() {
this.d = new Object;
this.connect = function(app) {
console.log(app);
$.ajax({
url: 'my working url which gives me my jsonp object',
dataType: 'jsonp',
jsonp: 'jsoncallback',
timeout: 5000,
success: function(data) {
this.d = data;
},
async: false
});
}
this.info = function() {
console.log(this.d);
}
}
I was wondering if there might be a problem with synchronizing? I'm not sure so I wanted to ask you guys.
jQuery $.ajax (and similar methods, like $.getJSON()) will return a Deferred object. Like other JavaScript objects, this is a "thing" which you can store in a variable and pass around in your code. Then you can add (async) callbacks to it at any time:
// Don't include a 'success' callback yet.
$def = $.ajax(...);
// $def is an object which you can store globally
// or pass as an argument into other functions.
//
// Somewhere else, add a success callback:
$def.done(function(data) {
// ...
});
See http://api.jquery.com/category/deferred-object/
If you refactor your code to return a deferred object thus:
function webService() {
this.d = new Object;
this.connect = function(app) {
console.log(app);
return $.ajax({
url: 'my working url which gives me my jsonp object',
dataType: 'jsonp',
jsonp: 'jsoncallback',
timeout: 5000,
success: function(data) {
this.d = data;
}
});
}
this.info = function() {
console.log(this.d);
}
}
You can then use done:
var web = new webService();
web.connect(app.info).done(function() {web.info();});
this also means your A jax is asynchronous, like it should be.
You could argue if your going down this route though, what is your function webservice even doing. Why not just let deferred do all the work?
this.connect = function(app) {
console.log(app);
var that = this; //store this into a variable
$.ajax({
url: 'my working url which gives me my jsonp object',
dataType: 'jsonp',
jsonp: 'jsoncallback',
timeout: 5000,
success: function(data) {
that.d = data; //use that so you have the right scope
},
async: false
});
}
other option is to use bind or jQuery's proxy
I have the following script to add a new value to the array of my session variable, and show me the varible totally live session
(function ($) {
Drupal.behaviors.MyfunctionTheme = {
attach: function(context, settings) {
$('.add-music').click(function () {
var songNew = JSON.stringify({
title: $(this).attr('data-title'),
artist: $(this).attr('data-artist'),
mp3: $(this).attr('href')
});
var songIE = {json:songNew};
$.ajax({
type: 'POST',
data: songIE,
datatype: 'json',
async: true,
cache: false
});
var session;
$.ajaxSetup({cache: false})
$.get('/getsession.php', function (data) {
session = data;
alert(session);
});
});
}}
})( jQuery );
the problem is that the POST shipping takes longer than the call GET ALERT then shows me the session variable not updated.
Is there a way to put an IF condition for shipping only when POST is complete return the response I GET?
thanks
Actually, what you want do do is to use a callback - that is, a function that is called as soon as your POST ajax request returns.
Example:
(function ($) {
Drupal.behaviors.MyfunctionTheme = {
attach: function(context, settings) {
$('.add-music').click(function () {
var songNew = JSON.stringify({
title: $(this).attr('data-title'),
artist: $(this).attr('data-artist'),
mp3: $(this).attr('href')
});
var songIE = {json:songNew};
$.ajax({
type: 'POST',
data: songIE,
datatype: 'json',
async: true,
cache: false
})
.done(
//this is the callback function, which will run when your POST request returns
function(postData){
//Make sure to test validity of the postData here before issuing the GET request
var session;
$.ajaxSetup({cache: false})
$.get('/getsession.php', function (getData) {
session = getData;
alert(session);
});
}
);
});
}}
})( jQuery );
Update per Ian's good suggestion I've replaced the deprecated success() function with new done() syntax
Update2 I've incorporated another great suggestion from radi8
This question already has answers here:
Closed 10 years ago.
Possible Duplicate:
Keeps saying result property is not defined. Why?
I am parsing xml and what I want is to return xml after parsing it. The problem is that in my getResult() function result has a value of undefined. Why and how can I make it work?
Here is my code
var result = '';
var Xml = {
to : null,
from : null,
url : null,
init: function (fromaddress, toaddress, link) {
from = fromaddress;
to = toaddress;
url = link;
this.requestXml();
return this;
},
requestXml: function () {
$.ajax({
type: "GET",
url: url,
dataType: "xml",
success: this.parseXml
});
},
parseXml: function (xml) {
console.log('xml: ' + $(xml));
result = $(xml);
},
getResult: function () {
console.log('Result: ' + Xml.result); //<--- Here result has undefined value
return result;
}
};
Xml.result is never being set. Maybe I'm missing something, but you will need to do this in parseXml in order to set its result field:
this.result = $(xml);
And in getResult your console.log call shouldn't show undefined. You can even have it return this.result.
Another thing, in your initializer, you should be setting each property with the "this" keyword.
Here is how I solved my issue
var Xml = function () {
var to, from, url, result,
init = function (fromaddress, toaddress, link, callback) {
from = fromaddress;
to = toaddress;
url = link;
requestXml(callback);
},
requestXml = function (callback) {
$.ajax({
type: "GET",
url: url,
dataType: "xml",
success: callback
});
},
getResult = function () {
return result;
};
return {
init : init,
getResult : getResult
};
};