How to line up jQuery deferred objects? - javascript

I need to delay every Ajax call until the previous function (hashcode.Sign()) has been completed. Unfortunately my code doesn't wait until hashcode.Sign() is completed and that messes everything up, since that function creates a new session and it cannot be overwritten in order to things to work. How should I do that? I'm new to Deferred objects so pardon my lack of knowledge.
for (var l = 0; l < fileArray.length; l++) {
var d = new jQuery.Deferred();
d.resolve(openDdoc(fileArray[l].url, $pid, fileArray[l].id));
d.done(function() { console.log('done'); });
}
function openDdoc(url, pid, fid) {
var def = new jQuery.Deferred();
$.when(def).done(function() { console.log('Function completed!'); });
def.resolve($.ajax({
url: '/open-ddoc',
type: 'post',
async: false,
data: {doc: url, pid: pid, fid: fid},
}).done(function (data) {
hashcode.Sign();
}).fail(function(data) {
console.log('this shit failed');
}));
}
The hashcode.Sign() function includes some more Ajax calls and calls to other functions. I can include it later if neede. I know I could resolve all of this with setTimeout(), but that wouldn't be my first choice.

It's not immediately obvious but the sequentiality you seek can be expressed in the form of fileArray().reduce(...).
This tried and tested pattern is outlined here under the subheading "The Collection Kerfuffle". (The whole article is well worth reading).
fileArray.reduce(function(promise, file) {
return promise.then(function() {
return $.ajax({
url: '/open-ddoc',
type: 'post',
async: true, // <<<<< always TRUE!!
data: {doc: file.url, pid: $pid, fid: file.id}
});
}).then(function(data) {
console.log(file.id + ': done');
return hashcode.Sign(); // hashcode.Sign() must return a promise
});
}, $.when()) // <<<<< resolved promise to get the chain started
.then(function() {
console.log('all done');
});
In this pattern, each member of fileArray is visited in turn (synchronously), adding a fresh .then() to a growing promise chain at each visit.
The resolved starter promise, $.when(), causes the constructed chain to start settling, and the ajax calls are made in sequence.
The ajax call doesn't need to be in a separate function, unless it's going to be called elsewhere.
It should be emphasised that hashcode.Sign() (which we understand to be asynchronous) must be written in the right way such that it returns a valid promise. If not, then the settlement process has no way of ensuring its asynchronism(s) to be complete before proceeding.

Related

JavaScript promise in recursive function

I have a function in my application that must execute multiple times for pagination, however after the first time, resolve never executes again.
Am I missing something?
function start(){
$.when(myrequest()).done(function(result){
console.log(result);
})
}
function myrequest(myurl){
//NOTE this is just an example but the idea is the same.
return new Promise(function(resolve, reject){
$.ajax({
url: myurl,
method: "GET",
contentType: "application/json",
headers:{
Authorization: key
},
success: function(response) {
if (response.currenturl !== response.lasturl) {
myurl = response.nexturl;
//Do stuff with data here as well to create a list.
myrequest(myurl);
//RESOLVES JUST FINE
resolve("test");
}else{
//DOESN'T RESOLVE
resolve("test");
}
},
error: function(response){
reject("error");
}
})
})
}
Like zero said you gotta propagate the recursive promise by returning that in the resolve.
success: function(response) {
if (response.currenturl !== response.lasturl) {
myurl = response.nexturl;
//Do stuff with data here as well to create a list.
// *heres magic
resolve(myrequest(myurl));
your myrequest(...); call returns a promise, and on the monitor of that promise object you are performing further, so whenever that promise is resolved, that returned object's (then/done) handler will invoke, but in your recursive call of myrequest(myurl); , what that returns (A promise) you are not using that, and you are loosing that promise (kind of leak), so obviously, even if that promise is resolved, no one have a reference to that, so they can perform something for listening to resolve.
You are not using the recursive result (the promise) of myrequest.
For instance, you can do this:
if (response.currenturl !== response.lasturl) {
myurl = response.nexturl;
//Do stuff with data here as well to create a list.
$.when(myrequest(myurl)).done(function (result) {
//RESOLVES JUST FINE
resolve("test");
});
}
You actually don't need to create a new promise with new Promise, since $.ajax returns a promise already. Also, you don't need $.when which is mostly for dealing with an array of promises, but you only pass one promise to it. Instead, use the then method of the promise.
Something like this:
function start(startUrl){
return myrequest(startUrl).then(function(result){
console.log(result);
});
}
function myrequest(myurl, collected = []){
return $.ajax({
url: myurl,
method: "GET",
contentType: "application/json",
headers:{
Authorization: key
}
}).then(function(response) {
if (response.currenturl !== response.lasturl) {
// add something to the partial results,
// e.g. the url, but could be response data:
collected.push(myurl);
//Do stuff with data here as well to create a list.
//...
// Also pass on the partial results:
return myrequest(response.nexturl, collected);
} else {
return collected; // return all the results
}
});
}

How to use promises, or complete an ajax request before the function finishes?

I have the following function to check a users session to see if they're staff or not. Now, I know there are better ways to do this, but I'm trying to make a simple application that's tied with a forum software.
function isStaff(callback) {
$.ajax({
url: url
}).done(function(data) {
var session = $.parseJSON(data);
if (session.is_staff === 1) {
callback(true);
} else {
callback(false);
}
});
}
Let's say I'm using this function in, like so, when compiling a "post" (Handlebars).
function compilePost(post) {
var source = $('#feed-item-template').html();
var template = Handlebars.compile(source);
var context = {
id: post.id,
content: post.text,
author: post.author,
date: $.timeago(post.date),
staff: function() {
isStaff(function(response) {
return response;
});
}
}
var html= template(context);
return html;
}
Problem here, is that the request to check if a user is staff doesn't complete the request until after the function is ran.
I know with Promises is an alternative to async: false, where request is made and the response comes back before the function finishes.
But I have no idea how I can convert this into a promise. I've tried to learn it but I'm stuck at the concept. Can someone explain this to me? Thanks.
First, let's simplify the compilePost function. This function should know how to compile a post in a synchronous manner. Let's change the isStaff fetching to a simple argument.
function compilePost(post, isStaff) {
var source = $('#feed-item-template').html();
var template = Handlebars.compile(source);
var context = {
id: post.id,
content: post.text,
author: post.author,
date: $.timeago(post.date),
staff: isStaff
}
var html= template(context);
return html;
}
Now, let's create a new method, with a single purpose - checking if a user is member of the staff:
function checkForStaffMemebership() {
return new Promise(function (resolve, reject) {
$.ajax({
url: url,
success: function (data) {
var session = $.parseJSON(data);
if (session.is_staff === 1) {
resolve(true);
} else {
resolve(false);
}
}
});
});
}
This function wraps your original ajax call to the server with a promise, whenever the $.ajax call gets a response from the server, the promise will resolve with the answer whether the user is a staff member or not.
Now, we can write another function to orchestrate the process:
function compilePostAsync(post) {
return checkForStaffMemebership()
.then(function (isStaff) {
return compilePost(post, isStaff);
});
}
compilePostAsync finds out whether the user is a staff member or not. Then, it's compiling the post.
Please notice that compilePostAsync returns a promise, and thus if you used to have something like:
element.innerHTML = compilePost(post);
Now, you should change it to something like:
compilePostAsync(post).then(function (compiledPost) {
element.innerHTML = compiledPost;
});
Some notes:
This is only an example, it surely misses some things (proper error handling for example)
The isStaff and checkForStaffMemebership (original and new) do not get any argument, I guess you'd figure out how to pass the userId or any other data you might need
Read about promises, it's a useful tool to have, there is a lot of data about it on the web, for example: MDN.
As per the documentation you dont need to wrap the ajax with a promise which already implements promise. Instead chain the response as explained below.
The jqXHR objects returned by $.ajax() as of jQuery 1.5 implement the Promise interface, giving them all the properties, methods, and behavior of a Promise (see Deferred object for more information)
You can do something like below by chaining the response:
function isStaff(url, post) {
return $.ajax({
url: url,
dataType:"json"
}).then(function(resp){
//resp = $.parseJSON(resp); /*You dont require this if you have respose as JSON object. Just Specify it in 'dataType'*/
var source = $('#feed-item-template').html();
var template = Handlebars.compile(source);
var context = {
id: post.id,
content: post.text,
author: post.author,
date: $.timeago(post.date),
staff: resp.is_staff === 1 ? true : false
};
return template(context);
});
}
isStaff(url, post).done(function(template){
/*Your compiled template code is available here*/
}).fail(function(jqXHR, textStatus, errorThrown){
console.log("Error:"+textStatus);
});
Note: Be sure to implement error callbacks also. Because you may never know what
went wrong :)
Simple explanation about promise with $.defer:
For understanding i have created the Fiddle similar to your requirement.
Explanation:
Basically Promise is been introduced to attain synchronous execution of asynchronous JS code.
What do you mean by Async or Asynchronous code?
The code that is executed may return a value at any given point of time which is not immediate. Famous example to support this statement would be jquery ajax.
Why is it required?
Promise implementations helps a developer to implement a synchronous code block which depends on asynchronous code block for response,. like in ajax call when i make a request to server asking for a data string, i need to wait till the server responds back to me with a response data string which my synchronous code uses it to manipulate it , do some logic and update the UI.
Follow this link where the author has explained with detailed examples.
PS: Jquery $.defer implements or wraps promise in quite a different way. Both are used for the same purpose.
let basedataset = {}
let ajaxbase = {};
//setting api Urls
apiinterface();
function apiinterface() {
ajaxbase.createuser = '/api/createuser'
}
//setting up payload for post method
basedataset.email = profile.getEmail()
basedataset.username = profile.getGivenName()
//setting up url for api
ajaxbase.url = ajaxbase.createuser
ajaxbase.payload = basedataset;
//reusable promise based approach
basepostmethod(ajaxbase).then(function(data) {
console.log('common data', data);
}).catch(function(reason) {
console.log('reason for rejection', reason)
});
//modular ajax (Post/GET) snippets
function basepostmethod(ajaxbase) {
return new Promise(function(resolve, reject) {
$.ajax({
url: ajaxbase.url,
method: 'post',
dataType: 'json',
data: ajaxbase.payload,
success: function(data) {
resolve(data);
},
error: function(xhr) {
reject(xhr)
}
});
});
}
A solution using async await in js would be like this:
async function getMyAjaxCall() {
const someVariableName = await ajaxCallFunction();
}
function getMyAjaxCall() {
return $.ajax({
type: 'POST',
url: `someURL`,
headers: {
'Accept':'application/json',
},
success: function(response) {
// in case you need something else done.
}
});
}

List of nested AJAX calls and $.when.apply - deferred promises not working right

I'm trying to make a triple nested series of AJAX calls; the basic structure is below (I've omitted fail calls).
It's working up to the second level with the eventCalls. The final when.apply.done only triggers after every single call to event.eventUsers.href has finished, as expected. But the third ajax call, the one inside the done of the event.eventUser.href call that retrieves user information, executes after the console.log in the when block. I'm sure I'm just not understanding the deferred/promises concept completely enough, could anyone clear this up? Thanks!
$.ajax({
type: 'GET'
url: '/api/events'
}).done(function(events, textStatus, jqXHR) {
var eventCalls = [];
$.each(events.items, function(index, event) {
eventCalls.push(
$.ajax({
type: 'GET',
url: event.eventUsers.href // Assoc objects for EventUser
data: 'type=host' // Only one eventUser is returned
}).done(function(eventUsers, textStatus, jqXHR) {
// Getting the eventUser's user information
$.ajax({
type: 'GET',
url: eventUsers.items[0].user.href
}).done(function(user, textStatus, jqXHR) {
event.host = user;
})
})
)
})
$.when.apply($, eventCalls).done(function() {
console.log(events);
})
})
Here's the whole thing simplified by using :
$.get() in place of $.ajax()
$.map() to create the array of promises
and corrected with :
.then() in place of .done() all through
appropriate returns from the .then callbacks.
$.get('/api/events').then(function(events) {
var queryString = 'type=host';//defined outside the $.map() loop for efficiency.
var promises = $.map(events.items, function(item) {//$.map() loops through events.items and returns an array
return $.get(item.eventUsers.href, queryString).then(function(eventUsers) {//note `return`
return $.get(eventUsers.items[0].user.href).then(function(user) {//note `return`
item.host = user;
return item;//this return determines the value with which the final promise is resolved.
});
});
});
//at this point, `promises` is an array of promises each of which will be resolved when its `item.host` has been set
$.when.apply(null, promises).then(function() {
console.dir(events);
});
});
As you've been instructed by Bergi the solution is to replace done with then inside the nested AJAX call and to return the value.
The difference between done and then is that done adds a handler and returns the same promise while then adds a handler and returns a new promise that resolves when what you return from the then resolves.
eventCalls.push(
$.ajax({
type: 'GET',
url: event.eventUsers.href,
data: 'type=host'
}).then(function(eventUsers) { // `then` chaines promises
return $.ajax({ // Promises chain with return values
type: 'GET',
url: eventUsers.items[0].user.href
}).then(function(user, textStatus, jqXHR) {
event.host = user; // synchronous so no big deal done would work
}).then(function(){ return event; }); // resolve with event to show
})
)
As a tip - you can use .map instead of .each.

Chaining multiple jQuery ajax requests

I have the following code:
$.when(loadProjects())
.then(function() {
$.when.apply($, buildRequests(projects))
.then(function(data) {
$.when.apply($, vcsRequests(buildTypes))
.then(function(data) {
$.when.apply($, vcsDetailRequests(vcsRoots))
.then(function(data) {
alert('done');
});
});
});
});
Each of the functions passed into when.apply() return arrays of requests. I cannot perform the buildRequests calls until the calls from loadProjects() has finished as they rely on information returned from those calls. Each call depends on information returned by the previous call, so they must be in this order. I need to know when all the calls have finished so I can process the data returned.
Is there a cleaner way to approach this?
I came across yepnope.js the other day. I haven't tried it myself yet, but it might be helpful if you're doing a lot of ajax loading.
Actually thinking this over makes me realize that yepnope.js is not really applicable to your case. What I would consider in your case is to have loadProjects() et al return a single promise by applying when() internally in each function. Also putting pipe() to use could lead to something like
loadProjects().pipe(buildRequests).pipe(vcsRequests).pipe(vcsDetailRequests);
Sample buildRequests():
function buildRequests(projects){
// Do something using projects
// ...
var requestsPromise = ...; // Finally get ajax promise for requests
return requestPromise;
}
The result of the requestPromise will then be passed into the next piped function once it is resolved/rejected.
From the docs on pipe():
// Example: Chain tasks:
var request = $.ajax( url, { dataType: "json" } ),
chained = request.pipe(function( data ) {
return $.ajax( url2, { data: { user: data.userId } } );
});
chained.done(function( data ) {
// data retrieved from url2 as provided by the first request
});
.... response according to my comment on original post:
Seems you have lot of requests to chain. I would then consider
combining all request into single one.... much more efficient than
chaining...
Well, something like this:
PHP:
$projects = YourAPI::loadProjects();
$builds = YourAPI::getBuilds($projects);
$vcs = YourAPI::getVCS($builds);
$details = YourAPI::getVCSDetails($vcs);
// for example
return json_encode($details);
// OR, if you need all the data
$results = array(
"projects" => $projects,
"builds" => $builds,
"vsc" => $vcs,
"details" => $details
);
return json_encode($results);
This way, you have inherent synhronization between calls AND less HTTP trafiic ;)
Dependence chain of AJAX requests : You can chain multiple AJAX request — for example, first call retrieves the user details of a user, and we need to pass that value to second script. Remember that $.then() returns a new promise, which can be subsequently passed to the $.done() or even another $.then() method.
var a1 = $.ajax({
url: '/first/request/url',
dataType: 'json'
}),
a2 = a1.then(function(data) {
// .then() returns a new promise
return $.ajax({
url: '/second/request/url',
dataType: 'json',
data: data.userId
});
});
a2.done(function(data) {
console.log(data);
});

How to chain ajax requests?

I have to interact with a remote api that forces me to chain requests. Thats a callback-hell in asynchronous mode:
// pseudocode: ajax(request_object, callback)
ajax(a, function() {
ajax(b(a.somedata), function() {
ajax(c(b.somedata), function() {
c.finish()
}
})
})
It would be much more readable in sync mode:
sjax(a)
sjax(b(a.somedata))
sjax(c(b.somedata))
c.finish()
But Sjax is evil :) How do I do that in a nice not-so-evil and readable way?
You could have a single function which is passed an integer to state what step the request is in, then use a switch statement to figure out what request needs to be make next:
function ajaxQueue(step) {
switch(step) {
case 0: $.ajax({
type: "GET",
url: "/some/service",
complete: function() { ajaxQueue(1); }
}); break;
case 1: $.ajax({
type: "GET",
url: "/some/service",
complete: function() { ajaxQueue(2); }
}); break;
case 2: $.ajax({
type: "GET",
url: "/some/service",
complete: function() { alert('Done!'); }
}); break;
}
}
ajaxQueue(0);
Hope that helps!
Don't use anonymous functions. Give them names. I don't know if you're able to do what I wrote below though:
var step_3 = function() {
c.finish();
};
var step_2 = function(c, b) {
ajax(c(b.somedata), step_3);
};
var step_1 = function(b, a) {
ajax(b(a.somedata), step_2);
};
ajax(a, step_1);
This function should chain together a list of ajax requests, if the callbacks always return the parameters necessary for the next request:
function chainajax(params, callbacks) {
var cb = shift(callbacks);
params.complete = function() {
var newparams = cb(arguments);
if (callbacks)
chainajax(newparams, callbacks);
};
$.ajax(params);
};
You can define these callback functions separately and then chain them together:
function a(data) {
...
return {type: "GET", url: "/step2.php?foo"}
};
// ...
function d(data) { alert("done!"); };
chainajax({type: "GET", url: "/step1.php"},
[a, b, c, d]);
You could also declare the functions "inline" in the call to chainajax, but that might get a little confusing.
Maybe what you can do is write a server-side wrapper function. That way your javascript only does a single asynchronous call to your own web server. Then your web server uses curl (or urllib, etc.) to interact with the remote API.
Update: I've learn a better answer for this if you are using jQuery, see my update under the title: Using jQuery Deffered
Old answer:
You can also use Array.reduceRight (when it's available) to wrap the $.ajax calls and transform a list like: [resource1, resource2] into $.ajax({url:resource1,success: function(...) { $ajax({url: resource2... (a trick that I've learn from Haskell and it's fold/foldRight function).
Here is an example:
var withResources = function(resources, callback) {
var responses = [];
var chainedAjaxCalls = resources.reduceRight(function(previousValue, currentValue, index, array) {
return function() {
$.ajax({url: currentValue, success: function(data) {
responses.push(data);
previousValue();
}})
}
}, function() { callback.apply(null, responses); });
chainedAjaxCalls();
};
Then you can use:
withResources(['/api/resource1', '/api/resource2'], function(response1, response2) {
// called only if the ajax call is successful with resource1 and resource2
});
Using jQuery Deffered
If you are using jQuery, you can take advantage of jQuery Deffered, by using the jQuery.when() function:
jQuery.when($.get('/api/one'), $.get('/api/two'))
.done(function(result1, result2) {
/* one and two is done */
});
Check out this FAQ item on the jQuery site. Specially the callback reference and the complete method.
What you want is data from A to be passed to B and B's data passed to C. So you would do a callback on complete.
I haven't tried this though.
I believe that implementing a state machine will make the code more readable:
var state = -1;
var error = false;
$.ajax({success: function() {
state = 0;
stateMachine(); },
error: function() {
error = true;
stateMachine();
}});
function stateMachine() {
if (error) {
// Error handling
return;
}
if (state == 0) {
state = 1;
// Call stateMachine again in an ajax callback
}
else if (state == 1) {
}
}
I made a method using Promises
// How to setup a chainable queue method
var sequence = Promise.resolve();
function chain(next){
var promise = new Promise(function(resolve){
sequence.then(function(){
next(resolve);
});
});
sequence = promise;
}
// How to use it
chain(function(next){
document.write("<p>start getting config.json</p>");
setTimeout(function(){
document.write("<p>Done fetching config.json</p>");
next();
}, 3000);
});
chain(function(next){
document.write("<p>start getting init.js</p>")
setTimeout(function(){
document.write("<p>starting eval scripting</p>");
next();
}, 3000);
});
chain(function(next){
document.write("<p>Everything is done</p>");
});
Bonus: A ultraligth 138 byte limited A- Promise (that can only resolve - without parameters, and only call the last then-method )
Background:
I made this for node.js at the point where it dose not have promises ATM. I didn't want a complete full blown Promise library that I was dependent on and had to include in my package.json, I needed it to be fast and light and do mostly one thing only. I only needed it for one thing (chaining things like you want to)
function Q(a,b){b=this;a(function(){b.then&&b.then();b.then=i});return b}function i(a){a&&a()}Q.prototype={then:function(a){this.then=a}};
How?
// Start with a resolved object
var promise = new Q(function(a){a()});
// equal to
// var promise = Promise.resolve();
// example usage
new Q(function(resolve){
// do some async stuff that takes time
// setTimeout(resolve, 3000);
}).then(function(){
// its done
// can not return a new Promise
}); // <- can not add more then's (it only register the last one)
and for the chainable queue method
// How to setup a chainable queue method with ultraligth promise
var sequence = new Q(function(a){a()});
function chain(next){
var promise = new Q(function(resolve){
sequence.then(function(){
next(resolve);
});
});
sequence = promise;
}
The complete callback is what you're looking for:
$.ajax({
type: 'post',
url: "www.example.com",
data: {/* Data to be sent to the server. It is converted to a query string, if not already a string. It's appended to the url for GET-requests. */},
success:
function(data) {
/* you can also chain requests here. will be fired if initial request is successful but will be fired before completion. */
},
complete:
function() {
/* For more a more synchronous approach use this callback. Will be fired when first function is completed. */
}
});

Categories

Resources