I would like to learn how to return a value from one function and use it in another one.
I can show the value in a div or get it in an alert window but I need to use the value in the other function.
<script type="text/javascript">
SP.SOD.executeOrDelayUntilScriptLoaded(testCase,"sp.js");
function showUserInfo(){
var clientContext = SP.ClientContext.get_current();
var user = clientContext.get_web().get_currentUser();
clientContext.load(user);
clientContext.executeQueryAsync(function(){
return user.get_loginName();
},function(sender,args){alert(args.get_message());})
}
function testCase() {
var test = showUserInfo();
alert(test);
}
</script>
If you need to support Internet Explorer
To support Internet Explorer, you should use a callback function similar to what #Amos_MSFT suggested. Below you will find my solution, which is quite similar to the solution posted by #Amos_MSFW but with a few differences as well as comments.
// Execute the testCase() function only after sp.js has loaded and is ready.
SP.SOD.executeFunc('sp.js', 'SP.ClientContext', function() {
textCase();
});
function testCase() {
getLoginName(function(error, success) {
if (error) {
alert(error);
} else {
/*
* The line below is not really necessary, as the result of the
* getLoginName() function is already available in the success
* variable. You can already pass it to other functions, like
* this: alert(success);
*/
var loginName = success;
alert(loginName);
}
});
}
function getLoginName(callback) {
var clientContext = SP.ClientContext.get_current();
var user = clientContext.get_web().get_currentUser();
clientContext.load(user);
clientContext.executeQueryAsync(
function() {
/*
* The first argument of the callback() function is used for the
* error message, so we need to use null as the first argument
* when we want to return the login name as the success message.
*/
callback(null, user.get_loginName());
},
function(_sender, args) {
callback(args.get_message());
}
);
}
If you do not need to support Internet Explorer
I suggest you use a Promise if you don’t need to support Internet Explorer. A Promise is a special type of object that represents an operation that is yet to complete, and they make working with asynchronous operations easy and almost fun. I am not fluent enough to explain them in detail, so I advice you to read the article linked above if you are interested. What I will tell you, though, is that we can use a Promise to make sure the testCase() function stops and waits for the showUserInfo() function to finish, as in waits for the login name to be available.
I have not had the chance to test the code below, but it should work. If not, please let me know. I have also added a few comments in case you are not already familiar with Promises.
const showUserInfo = () => {
return new Promise((resolve, reject) => {
const clientContext = SP.ClientContext.get_current();
const currentUser = clientContext.get_web().get_currentUser();
clientContext.load(currentUser);
clientContext.executeQueryAsync(
() => {
/*
* We resolve the Promise when the query has executed successfully.
* Resolving a Promise means marking it as fullfilled, or complete, and
* returning the current user's login name.
*/
resolve(currentUser.get_loginName());
},
(_sender, args) => {
/*
* If something goes wrong, we reject the Promise. Rejecting means
* marking it as failed, while still returning something. In this
* case, we return the error message.
*/
reject(args.get_message());
}
);
});
}
const testCase = async () => {
/*
* We use the await keyword to halt the code and wait for the showUserInfo()
* function to finish. What really happens is that we wait for the Promise
* in the showUserInfo() function to be marked as settled, whether it is
* fullfilled or rejected, and then assign the result to the test constant.
*/
const test = await showUserInfo();
alert(test);
}
SP.SOD.executeOrDelayUntilScriptLoaded(testCase, 'sp.js');
Function executeQueryAsync is asynchronous. You can use the callback function to get the return value.
Sample code for your reference:
<script type="text/javascript">
SP.SOD.executeFunc('sp.js', 'SP.ClientContext', function(){
var testCase=function(callback){
var clientContext = SP.ClientContext.get_current();
var user = clientContext.get_web().get_currentUser();
clientContext.load(user);
clientContext.executeQueryAsync(function(){
callback(null,user.get_loginName()) ;
},function(sender,args){alert(args.get_message());})
}
testCase(function(error,name){
if (error) {
console.error(error);
return;
}
console.log(name);
})
});
</script>
Related
I am looking to fire a series of functions after a few other functions have run and loaded content to my webpage. I have used setTimeOuts but I understand it's poor practice given there could be a lag in the connection, in which case they would run incorrectly. How do I do functions sequentially without using setTimeOuts.
The avgEmotion and avgSentiment functions are supposed to run once a series of lists are created on the webpage through the newsApiLeft() and newsApiRight() functions.
Thanks so much in advance.
button.addEventListener("click", function () {
var filter = document.getElementById("news_cat");
var filterSource = document.getElementById("news_source");
var category = filter.value;
var source = filterSource.value;
params.sources = source;
params.q = category;
parameters.q = category;
var filterL = document.getElementById("news_sourceL");
var sourceL = filterL.value;
parameters.sources = sourceL;
showContent();
clearTextRight();
clearTextLeft();
newsApiLeft();
newsApiRight();
setTimeout(avgEmotionR, 2000);
setTimeout(avgEmotionL, 2000);
setTimeout(avgSentiment, 2000);
})
The right way to execute a function after another one returns (asynchronous calls) is by using Promises. Make your newsApiLeft() and newsApiRight functions returning a Promise and then call them like follows:
var newsApiLeft = new Promise(function(resolve, reject) {
// do something long running
let everythingWasOK = true;
if (everythingWasOK) {
resolve("I can return a value");
//or just resolve();
} else {
reject(Error("Or error"));
}
});
newsApiLeft.then((returnedData)=>{
avgEmotionL();
//run another function here;
//you can use the returnedData
}, (error)=>{
//optionally handle error
})
You can achieve this using JavaScript promises.
Here i created a Promise A, where I do my logic processing at the comment mentioned below. Then you need to either resolve or reject it based on your logic. If you resolve then() function will be called.
In the first then() function you can nest new promise. Like wise you can nest as many as you want. This way you can make sure promise B will execute only after end of promise A.
var A = new Promise(function(resolve, reject) {
// Do an async task here and then...
if(/* good condition */) {
resolve('Success!');
}
else {
reject('Failure!');
}
});
A.then(function() {
/* do something with the result */
}).catch(function() {
/* error :( */
})
For executing promise A just do A();
You could achieve this using callbacks:
function newsApiLeft(callback) {
// Load stuff
// Then call the callback; the code you want
// to run after the loading is complete
callback();
}
newsApiLeft(function() {
avgEmotion();
avgSentiment();
});
If you're using the same callback for both Left and Right, you could save the callback and use it for both:
var callback = function() {
avgEmotion();
avgSentiment();
}
newsApiLeft(callback);
newsApiRight(callback);
I'm trying to figure out Promises with Parse.
What I want to do is get a lot of jobs, and then perform an update for each job.
var queryJobs = new Parse.Query("Jobs");
queryJobs.find().then(function (results) {
// Loop thorugh all jobs
for (var i = 0; i < results.length; i++) {
var job = results[i];
// Here. I want to run an update on all object and then continue.
}
return ????;
}).then(function () {
status.success("Finish");
}, function () {
status.error("Error");
});
I tried this without luck. The push block is never executed.
var queryJobs = new Parse.Query("Jobs");
queryJobs.find().then(function (results) {
var promises = [];
// Loop thorugh all jobs
for (var i = 0; i < results.length; i++) {
var job = results[i];
promises.push((function () {
// This is never executed.
var promise = new Parse.Promise();
var query = new Parse.Query("Jobs");
query.first({
success: function (object) {
// ... Do something here....
promise.resolve();
},
error: function () {
promise.resolve();
}
});
promise.resolve();
return promise;
}));
}
return Parse.Promise.when(promises);
}).then(function () {
status.success("Finish");
}, function () {
status.error("Error");
});
Thanks in advance
UPDATE
I've changed the code, and I get into the callback, however, the query is not executed.
...
promises.push((function () {
// GET HERE
var promise = new Parse.Promise();
var query = new Parse.Query("Jobs");
query.first({
success: function (object) {
console.log("CALLBACK");
promise.resolve();
},
error: function () {
console.log("CALLBACK");
promise.resolve();
}
});
}()));
return Parse.Promise.when(promises);
Here is how I would set this up:
var failure = new Parse.Promise();
var success = new Parse.Promise();
var queryJobs = new Parse.Query("Jobs");
queryJobs.each
(
function( job )
{
//Do your changes to the job
return job.save().then
(
function( job )
{
return Parse.Promise.as( "job saved" );
},
function( error )
{
failure.reject("There was an error trying to save a job: " + error.message);
return failure;
}
);
}
).then
(
function( results )
{
success.resolve("Successfully updated all the jobs" )
return success;
},
function( error )
{
failure.reject("There was an error trying to query for Jobs: " + error.message);
return failure;
}
).then
(
function( success )
{
response.success( success );
},
function( failure )
{
response.error( failiure );
}
);
This may not work out of the box, but it has a few key features that may help you.
1) I know that one of the perks that is mentioned in the blog post and what not announces promises is that you can get rid of pyramid code, but if you want descriptive error messages, the pyramid code is a necessary evil. My first promise (queryJobs.each in this case) always has two .then()'s. The second one always just does response.error( failure ) and response.success( success ).
2) I create two promises, although you can use just one. I prefer two so it is clear where I'm failing / succeeding. I return these where I reach a dead end/ the finish line.
3) I used query.each instead of query.find. query.find() is limited to 1000 results, which, while it will probably be more than enough for a long time, will eventually cause you to hit your limit, and you'd need to start paginating your results. Using query.each will perform your function on every single object that could be returned by the query. One perk of query.each vs query.find and iterating through the results is that query.each performs it's callback on each object asynchronously, rather than a linear iteration.
4) In this case it would probably be better just to have return job.save() inside of the each block, but I wanted to show how I do the nested promise returns. This is what allows me to have very specific success / error statements. This is important because even if one link in your promise chain fails, you will keep executing the next links. The exception to this is if a promise is rejected and you don't have an error function until your last chain. The error will get passed from link to link until it finds an error function, which is fine, except it limits how much you can customize your error messages.
I'll also note that what you have is probably going to return the same object again and again for that query.first() method, rather than working with the specific job from the first query. Like, you are iterating through your jobs, but instead of doing anything with each job, you're getting the first job and doing something with it again and again. I don't think that's what you actually wanted, but maybe this is meant to be a "learn promises" post rather than something functional.
Anyway, hope I helped a bit. Let me know if you have questions and I'll do my best to answer them.
edit: I know my style varies greatly from others'. I like opening and closing brackets on a new line, for the most part. I actually read in javascript that this can sometimes cause errors. I forget the specific cases, but this is not one of them. But feel free to edit the style back to how you prefer it.
You have to add promises to promises, not functions. You need to call the function so that it returns the promise:
promises.push((function () {
// ...
}()));
// ^^
Furthermore you have to remove the promise.resolve(); call before the return statement. The promise should only be resolved after the query succeeded. As it currently is, the promise is resolved immediately.
Please forgive me if this is a stupid question. I have been trying for hours and my brain have just stopped working.
I have such system that consists of three AJAX calls. Server response of first call usually is a 200 Success; but second and third queries are fragile because they are image uploading, and on the server side, I have so much validation rules that client's images mostly fail.
window.AjaxCall = function () {
// to pass to $.ajax call later
this.args = arguments;
// xhr status
this.status = null;
// xhr results (jqXHR object and response)
this.xhrResponse = {};
this.dfr = new $.Deferred();
// to provide an easier interface
this.done = this.dfr.done;
this.fail = this.dfr.fail;
this.then = this.dfr.then;
};
AjaxCall.prototype.resetDfr = function () {
this.dfr = new $.Deferred();
};
AjaxCall.prototype.resolve = function () {
this.dfr.resolve(
this.xhrResponse.result,
this.xhrResponse.jqXHR
);
this.resetDfr();
};
AjaxCall.prototype.reject = function () {
this.dfr.reject(
this.xhrResponse.jqXHR
);
this.resetDfr();
};
AjaxCall.prototype.query = function () {
var _this = this;
// if query hasn't run yet, or didn't return success, run it again
if (_this.status != 'OK') {
$.ajax.apply(_this, _this.args)
.done(function (result, textStatus, jqXHR) {
_this.xhrResponse.result = result;
_this.xhrResponse.jqXHR = jqXHR;
_this.resolve();
})
.fail(function (jqXHR) {
_this.xhrResponse.jqXHR = jqXHR;
_this.reject();
})
.always(function (a, b, c) {
var statusCode = (typeof c !== 'string'
? c
: a).status;
if (statusCode == 200) {
_this.status = 'OK';
}
});
}
// if query has been run successfully before, just skip to next
else {
_this.resolve();
}
return _this.dfr.promise();
};
AjaxCall class is as provided above, and I make the three consecutive calls like this:
var First = new AjaxCall('/'),
Second = new AjaxCall('/asd'),
Third = new AjaxCall('/qqq');
First.then(function () {
console.log('#1 done');
}, function() {
console.error('#1 fail');
});
Second.then(function () {
console.log('#2 done');
}, function() {
console.error('#2 fail');
});
Third.then(function () {
console.log('#3 done');
}, function() {
console.error('#3 fail');
});
var toRun = function () {
First.query()
.then(function () {
return Second.query();
})
.then(function () {
return Third.query()
});
};
$('button').click(function () {
toRun();
});
Those code are in a testing environment. And by testing environment, I mean a simple HTML page and basic server support for debugging.
Home page (/) always returns 200 Success.
/asd returns 404 Not Found for the first 3 times and 200 Success once as a pattern (i.e. three 404s -> one 200 -> three 404s -> one 200 -> three 404s -> ... ).
/qqq returns 404 Not Found all the time.
When I click the only button on the page, first query returns success and second fails as expected. When I click the button second time, first query skips because it was successful last time and second fails again, also as expected.
The problem here is:
before I used the resetDfr method because the dfr is alreay resolved or rejected, it doesn't react to resolve and reject methods anymore.
When I call the resetDfr method in the way I show in the example, dfr is able to get resolved or rejected again, but the callbacks of the old dfr are not binded with the new dfr object and I couldn't find a way to clone the old callbacks into the new dfr.
What would be your suggestion to accomplish what I'm trying to do here?
Promises represent a single value bound by time. You can't conceptually "reuse" a deferred or reset it - once it transitions it sticks. There are constructs that generalize promises to multiple values (like observables) but those are more complicated in this case - it's probably better to just use one deferred per request.
jQuery's AJAX already provides a promise interface. Your code is mostly redundant - you can and should consider using the existent tooling.
Let's look at $.get:
It already returns a promise so you don't need to create your own deferred.
It already uses the browser cache, unless your server prohibits HTTP caching or the browser refuses it only one request will be made to the server after a correct response arrived (assuming you did not explicitly pass {cache: false} to its parameters.
If making post requests you can use $.post or more generally $.ajax for arbitrary options.
This is how your code would roughly look like:
$("button").click(function(){
var first = $.get("/");
var second = first.then(function(){
return $.get("/asd");
});
var third = second.then(function(){
return $.get("/qqq");
});
});
The reason I put them in variables is so that you will be able to unwrap the result yourself later by doing first.then etc. It's quite possible to do this in a single chain too (but you lose access to previous values if you don't explicitly save them.
For the record - it wasn't a stupid question at all :)
I have a function that will poll a database ever x seconds. I am using the Q library so the function will return a promise. The function will ultimetly be used in a long chain of .then()s.
The function does give me the results that I expect but the function continues to run for 30-40 seconds after the results are returned. I cannot figure out why it would not exit right after I return.
var _ = require('lodash');
var pg = require('pg');
var Q = require('q');
connString = 'postgres://somedb_info';
var query = "SELECT * FROM job where jobid='somejobid123123'";
exports.run_poller = function () {
var deferred = Q.defer();
function exec_query(callback) {
pg.connect(connString, function(err, client, done) {
if(err) {
deferred.reject(err);
}
client.query(query, function(err, result) {
done();
if(err) {
return deferred.reject(err);
}
callback(result.rows[0]);
});
});
}
function wait_for(res){
if(res.status == 'COMPLETE') {
return deferred.resolve(res);
} else {
setTimeout(function(){
exec_query(wait_for);
}, 1000);
}
}
exec_query(wait_for);
return deferred.promise;
};
Just to test this I call the function from a main.js file like so:
var poller = require('./utils/poller').run_poller;
poller().then(console.log).catch(function(err) {console.log(err,'*');});
Why doesn't main.js exit right after the data is returned? Is there a better way to achieve this?
I see that pg maintains a connection pool. I would assume that it's either taking awhile to time out some shared resources or it just takes a little while to shutdown.
You may be able to just call process.exit(0) in your node app to force it to exit sooner if you know you're done with all your work.
Or, you may be able to find configuration settings that affect how the connection pool works.
On this doc page, there's an example like this that might help:
//disconnect client when all queries are finished
client.on('drain', client.end.bind(client));
You should read the doc for .end() to make sure you're using it correctly as there are some cases where it says it should not be called (though they may not apply if your done with all activity).
I have the following code that causes the two call to Webtrends to be cancelled (ie these two calls did not give a http 200 but a cancelled message in the network tab of the browser) when I call it
mercury.Tracking.logUsage("export", GetSelectedExportType(form));
mercury.Tracking.logUsage('exportchart', mercury.ChartContainer.currentChartUri(), path);
form[0].submit();
I rewrote this in this way to avoid this issue, as it seemed to me that the reason why the calls to Webtrends were being cancelled was because the form submit was making that happen so before calling submit on the form I wait two seconds.
mercury.Tracking.logUsage("export", GetSelectedExportType(form));
mercury.Tracking.logUsage('exportchart', mercury.ChartContainer.currentChartUri(), path);
var submit = function () {
setTimeout(function() {
form[0].submit();
}, 2000);
};
submit();
Question is, is there a better way, using promises or callbacks or whatever to do this?
The logUsage code is
(function ($, window) {
function Tracking() {
}
Tracking.prototype.chartTitle = function () {
return $('#chartNameInfo').text();
};
Tracking.prototype.hostName = function () {
return $('#trackingVars').data('host-name');
};
Tracking.prototype.page = function () {
return $('#trackingVars').data('page');
};
Tracking.prototype.currentUser = function () {
return window.config.userId;
};
Tracking.prototype.logUsage = function (action, resourceUri, actionTargetUri, additionalTags) {
// action: action performed - e.g. create, delete, export
// resourceUri: URI of API resource *on* which action is being performed (required), e.g. /users/current/annotations/{annotation-id}
// actionTargetUri: URI of API resource *to* which action is being performed (optional), e.g. /charts/{chart-id}
if (action.indexOf("DCSext.") < 0) {
action = "DCSext." + action;
}
var jsonString = '{"' + action + '"' + ':"1"}';
var jsonObj = JSON.parse(jsonString);
if (additionalTags == null) {
additionalTags = jsonObj;
}
else {
additionalTags = $.extend({}, additionalTags, jsonObj); //Append two JSON objects
}
var trackingargs = $.extend({
'DCSext.resource-uri': resourceUri,
'DCSext.action-target-uri': actionTargetUri,
'WT.ti': this.chartTitle(),
'DCSext.dcssip': this.hostName(),
'DCSext.em-user-id': this.currentUser(),
dsci_uri: this.page()
}, additionalTags);
try {
WebTrends.multiTrack({ args: trackingargs });
} catch (e) {
console.log(e);
}
};
window.Tracking = new Tracking();
$(function() {
$('body').on('click', 'a[data-tracking-action]', function() {
window.Tracking.logUsage($(this).data('tracking-action'), $(this).data('tracking-resource'));
});
$(document).on('attempted-access-to-restricted-resource', function(event, href) {
window.Tracking.logUsage('unauthorisedResourceAccessUpsell', href.url);
});
});
})(jQuery, window);
With the extra information provided, I think I can now answer your question.
From WebTrends doc, you can add a finish callback to your WebTrends.MultiTrack call.
What you could do:
Tracking.prototype.logUsage = function (action, resourceUri, actionTargetUri, additionalTags) {
...
var finished = $.Deferred();
...
try {
WebTrends.multiTrack({ args: trackingargs, finish: function(){finished.resolve();}});
}
...
return finished;
}
and then in your code:
$.when(mercury.Tracking.logUsage("export", GetSelectedExportType(form)),
mercury.Tracking.logUsage('exportchart', mercury.ChartContainer.currentChartUri(), path))
.done(function(){
form[0].submit();
});
I have not tested this, but I think it should work. Hope it helps.
Explanations:
jQuery.when()
Description: Provides a way to execute callback functions based on one
or more objects, usually Deferred objects that represent asynchronous
events.
Basically, jQuery.when() will take one or more deferreds (which build promises) or promises and will return one promise that fulfills when they all fulfill. From there, we can choose to add handlers using th e .done() or .then() method to our promise, which will be called once or promise is fulfilled . (A promise represents the result of an asynchronous operation).
So, in the code above, I created a new deferred object in your logUsage method, and that method returns the deferred, so you can pass those deferreds to jQuery.when method and when they will be fulfilled (this is why I added the finish callback in your WebTrends.Multitrack call), the handler passed to deferred.done() will be executed.
I hope this is not too confusing, I'm not sure I'm explaining it correctly.
Not trying to steal Antoine's rep. His answer is essentially fine, but the ... sections can be fleshed out far more efficiently than in the question, plus a few other points for consideration.
Tracking.prototype.logUsage = function (action, resourceUri, actionTargetUri, additionalTags) {
// action: action performed - e.g. create, delete, export
// resourceUri: URI of API resource *on* which action is being performed (required), e.g. /users/current/annotations/{annotation-id}
// actionTargetUri: URI of API resource *to* which action is being performed (optional), e.g. /charts/{chart-id}
try {
// you might as well wrap all the preamble in the try{}, just in case it it error-prone
if (action.indexOf("DCSext.") < 0) {
action = "DCSext." + action;
}
//trackingargs can be defined efficiently as follows, avoiding the need for the variable `jsonObj` and the ugly JSON.parse().
var trackingargs = $.extend({
'DCSext.resource-uri': resourceUri,
'DCSext.action-target-uri': actionTargetUri,
'WT.ti': this.chartTitle(),
'DCSext.dcssip': this.hostName(),
'DCSext.em-user-id': this.currentUser(),
'dsci_uri': this.page()
}, additionalTags || {}); // `additionalTags || {}` caters for missing or null additionalTags
trackingargs[action] = 1;//associative syntax gets around the limitation of object literals (and avoids the need for JSON.parse()!!!).
//to keep things tidy, return $.Deferred(fn).promise()
return $.Deferred(function(dfrd) {
WebTrends.multiTrack({
args: trackingargs,
finish: dfrd.resolve //no need for another function wrapper. `$.Deferred().resolve` and `$.Deferred().reject` are "detachable"
});
}).promise();//be sure to return a promise, not the entire Deferred.
} catch (e) {
console.log(e);
//Now, you should really ensure that a rejected promise is always returned.
return $.Deferred.reject(e).promise();//Surrogate re-throw.
}
};
see comments in code
As Tracking.prototype.logUsage can now return a rejected promise, and as you probably don't want .logUsage() failure to inhibit your form submission, you probably want to convert rejected promises to fulfilled.
$.when(
mercury.Tracking.logUsage("export", GetSelectedExportType(form)).then(null, function() {
return $.when();//resolved promise
}),
mercury.Tracking.logUsage('exportchart', mercury.ChartContainer.currentChartUri(), path).then(null, function() {
return $.when();//resolved promise
})
).done(function() {
form[0].submit();
});
It may seem to be an unnecessary complication to return a rejected promise then convert to success, however :
it is good practice to report asycnhronous failure in the form of a rejected promise, not simply log the error and return undefined.
window.Tracking.logUsage() may be called elsewhere in your code, where it is necessary to handle an error as an error.