How to Dodge Jquery Promises Completely When Chaining Two Async Jquery Functions

How to dodge jQuery promises completely when chaining two async jQuery functions?

You can adopt either of two approaches ...

Convert then combine :

var p1 = Promise.resolve($.getJSON(url_1, params_1)); // voila 1!
var p2 = Promise.resolve($.getJSON(url_2, params_2)); // voila 2!
var p3 = Promise.all([p1, p2]).then(...);

Combine then convert :

var p1 = $.getJSON(url_1, params_1);
var p2 = $.getJSON(url_2, params_2);
var p3 = Promise.resolve($.when(p1, p2)).then(...); // voila 1 and 2!

Straightforwardly, either approach will give you a native ES6 promise, p3, that resolves when both the jQuery promises resolve, or is rejected when either one of the promises fails.

However, you are probably interested in the results of the two getJSON() calls, and jQuery is awkward in this regard. jQuery's jqXHR promises pass multiple parameters to their success and error callbacks, whereas an ES6 promise will accept just one; the rest will
be disregarded. Fortunately, it's fairly simple to bundle the multiple params together to make a single object. This has to be done in jQuery prior to conversion to ES6.

The "convert then combine" code expands as follows :

var p1 = Promise.resolve($.getJSON(url_1, params_1).then(
function(data, textStatus, jqXHR) {
return { data:data, textStatus:textStatus, jqXHR:jqXHR };
},
function(jqXHR, textStatus, errorThrown) {
return { jqXHR:jqXHR, textStatus:textStatus, errorThrown:errorThrown };
}
));
var p2 = Promise.resolve($.getJSON(url_2, params_2).then(
function(data, textStatus, jqXHR) {
return { data:data, textStatus:textStatus, jqXHR:jqXHR };
},
function(jqXHR, textStatus, errorThrown) {
return { errorThrown:errorThrown, textStatus:textStatus, jqXHR:jqXHR };
}
));
var p3 = Promise.all([p1, p2]).then(
function(results) {
// results[0] will be an object with properties .data, .textStatus, .jqXHR
// results[1] will be an object with properties .data, .textStatus, .jqXHR
},
function(rejectVal) {
// rejectVal will be an object with properties .errorThrown, .textStatus, .jqXHR
}
);

The "combine then convert" approach is slightly trickier as the combined results appear (in jQuery) as an arguments list, which itself needs to be converted (still in jQuery) to an Array.

var p1 = $.getJSON(url_1, params_1).then(
function(data, textStatus, jqXHR) {
return { data:data, textStatus:textStatus, jqXHR:jqXHR };
},
function(jqXHR, textStatus, errorThrown) {
return { errorThrown:errorThrown, textStatus:textStatus, jqXHR:jqXHR };
}
);
var p2 = $.getJSON(url_2, params_2).then(
function(data, textStatus, jqXHR) {
return { data:data, textStatus:textStatus, jqXHR:jqXHR };
},
function(jqXHR, textStatus, errorThrown) {
return { errorThrown:errorThrown, textStatus:textStatus, jqXHR:jqXHR };
}
);
var p3 = Promise.resolve($.when(p1, p2).then(function() {
return [].slice.call(arguments);// <<< convert arguments list to Array
})).then(
function(results) {
// results[0] will be an object with properties .data, .textStatus, .jqXHR
// results[1] will be an object with properties .data, .textStatus, .jqXHR
},
function(rejectVal) {
// rejectVal will be an object with properties .errorThrown, .textStatus, .jqXHR
}
);

DEMO: resolved

DEMO: rejected

Correct way to chain two promises when the second promise depends on the first?

return Promise.resolve($.getJSON(url2, params2));

What you did was create an already resolved promise (via Promise.resolve) promise whose resolved value is the result of $.get(), which is also a promise. This is why "it doesn't even wait for the second call to finish" and "the result is a promise".

Also, you don't need to wrap the jQuery AJAX calls as they return promises. jQuery's promises act almost (but not entirely) the same as native promises.

Do this instead:

$.getJSON(url1, params1).then(function(result)){
return $.getJSON(url2, params2);
}).then(function(result)){
//result 2
});

Additionally, you're using jQuery. jQuery AJAX with promises can be done like this:

$.getJSON(url1, params1).then(function(result){
// do stuff
});

Listening to multiple promises can be done this way in jQuery:

$.when($.getJSON(...), $.getJSON(...)).then(function(res1, res2){
// do stuff
});

How to avoid hard-coded, chained asynchronous functions in Javascript/jQuery?

Overview

You have a couple of choices. You can have your code using those functions look like this, using callbacks:

getStuff(function(results) {
getMoreStuff(results, doSomethingWithStuff);
});

or like this, using jQuery's Deferred and Promise objects:

getStuff().then(getMoreStuff).then(doSomethingWithStuff):

Using callbacks

Have both getStuff and getMoreStuff accept an argument that is a callback to call when they're done, e.g.:

function getStuff(callback) {
// ^------------------------------ callback argument
$.ajax({
...
success: function(results) {
// other functions involving results
callback(results);
// ^------------------------------------ use the callback arg
}
});
}

...and similarly for getMoreStuff.

Using Deferred and Promise

jQuery's ajax function integrates with its Deferred and Promise features. You can just add return to your existing functions to make that work, e.g.:

function getStuff(callback) {
return $.ajax({
...
});
}

(Note: No need for the success callback.)

Then this code:

getStuff().then(getMoreStuff).then(doSomethingWithStuff);

does this:

  1. getStuff starts its ajax call and returns the Promise that call creates.

  2. When that ajax call completes and resolves the promise, getMoreStuff is called with the results of the ajax call as its first argument. It starts its ajax call.

  3. When getMoreStuff's ajax call completes, doSomethingWithStuff is called with the results of that call (the one in getMoreStuff).

It's important to use then, not done, in order to get the correct results passed on at each stage. (If you use done, both getMoreStuff and doSomethingWithStuff will see the results of getStuff's ajax call.)

Here's a full example using ajax:

Fiddle | Alternate Fiddle with the ajax calls taking one second each (makes it easier to see what's happening)

function getStuff() {
display("getStuff starting ajax")
return $.ajax({
url: "/echo/json/",
type: "POST",
data: {json: '{"message": "data from first request"}'},
dataType: "json"
});
}

function getMoreStuff(results) {
display("getMoreStuff got " + results.message + ", starting ajax");
return $.ajax({
url: "/echo/json/",
type: "POST",
data: {json: '{"message": "data from second request"}'},
dataType: "json"
});
}

function doSomethingWithStuff(results) {
display("doSomethingWithStuff got " + results.message);
}

getStuff().then(getMoreStuff).then(doSomethingWithStuff);

function display(msg) {
var p = document.createElement('p');
p.innerHTML = String(msg);
document.body.appendChild(p);
}

Output:

getStuff starting ajax

getMoreStuff got data from first request, starting ajax

doSomethingWithStuff got data from second request

You don't need to be using ajax to get the benefit of this, you can use your own Deferred and Promise objects, which lets you write chains like this:

one().then(two).then(three);

...for any situation where you may have asynchronous completions.

Here's a non-ajax example:

Fiddle

function one() {
var d = new $.Deferred();
display("one running");
setTimeout(function() {
display("one resolving");
d.resolve("one");
}, 1000);
return d.promise();
}

function two(arg) {
var d = new $.Deferred();
display("Two: Got '" + arg + "'");
setTimeout(function() {
display("two resolving");
d.resolve("two");
}, 500);
return d.promise();
}

function three(arg) {
var d = new $.Deferred();
display("Three: Got '" + arg + "'");
setTimeout(function() {
display("three resolving");
d.resolve("three");
}, 500);
return d.promise();
}

one().then(two).then(three);

function display(msg) {
var p = document.createElement('p');
p.innerHTML = String(msg);
document.body.appendChild(p);
}

Output:

one running

one resolving

Two: Got 'one'

two resolving

Three: Got 'two'

three resolving

These two (the ajax example and the non-ajax example) can be combined when necessary. For instance, if we take getStuff from the ajax example and we decide we have to do some processing on the data before we hand it off to getMoreStuff, we'd change it like this: Fiddle

function getStuff() {
// Create our own Deferred
var d = new $.Deferred();
display("getStuff starting ajax")
$.ajax({
url: "/echo/json/",
type: "POST",
data: {json: '{"message": "data from first request"}', delay: 1},
dataType: "json",
success: function(data) {
// Modify the data
data.message = "MODIFIED " + data.message;

// Resolve with the modified data
d.resolve(data);
}
});
return d;
}

Note that how we use that didn't change:

getStuff().then(getMoreStuff).then(doSomethingWithStuff);

All that changed was within getStuff.

This is one of the great things about the whole "promise" concept (which isn't at all specific to jQuery, but jQuery gives us handy versions to use), it's fantastic for decoupling things.

jQuery promise execution order differs from javascript promise

jQuery 2 does not support Promises/A+, and cannot assimilate promises from other implementations ("thenables"). The native promise that is returned from the callback and resolves the outer promise is not being awaited, but rather used as the immediate fulfillment value. You can see this by inspecting the argument passed to your final callback.

Is there a way to force the standard behavior?

See How to dodge jQuery promises completely when chaining two async jQuery functions?
Basically just wrap the $.post("index.php") that starts your chain in Promise.resolve to get a native promise with all its glory (and expected behavior).

The other way round (wrapping the native promise from the timeout in a jQuery promise) I can really not recommend, but it would basically come down to

return $.Deferred(def => promise.then(def.resolve, def.reject)).promise()

jQuery deferreds - order of execution in multiple blocks

Per jQuery's documentation on $.when():

Each argument [of .then()] is an array with the following structure: [ data, statusText, jqXHR ]

Meaning you could do something like this...

$.when(
$.post(myparams),
$.post(otherparams),
$.post(yetanother)
).then((res1, res2, res3) => { //Arg for each result
myfunc(res1[0]); //Call myfunc for result 1's data
myfunc(res2[0]); //Call myfunc for result 2's data
myfunc(res3[0]); //Call myfunc for result 3's data
});

Though perhaps a cleaner version might be something like this instead...

let  myarr = [],  myfunc = arg => myarr.push(arg);
$.when( $.get('https://jsonplaceholder.typicode.com/todos/1'), $.get('https://jsonplaceholder.typicode.com/todos/2'), $.get('https://jsonplaceholder.typicode.com/todos/3')).then((...results) => { //Get all results as an array results.map(r=>r[0]).forEach(myfunc); //Call myfunc for each result's data console.log(myarr);});
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>

Using jQuery when with array of promises

Because the promises in the array are promises for undefined, not promises for your templates. You'll need to use

$.get('/temps/' + file + '.hbs').then(function(temp) {
console.log('done loading', temp);
return temp;
// ^^^^^^
})

to get a promise for the template, otherwise it resolve with the implicit return value undefined. Remember that then returns a new promise for the result of the callback, not the original promise.

Return promise after multiple calls with the same instance of an Ajax method

If I've understood your question perfectly, you want to resolve a Promise if there is no offset in the response from your Ajax request.

I haven't tested this code but you can do something like this:

function getContracts(offset) {
return new Promise((resolve, reject) => {
var data = {};

if (offset !== undefined) {
data['offset'] = offset;
}

$.ajax({
url: url,
headers: {
Authorization: apiKey,
},
data: data,
success: function(result) {
$.each(result.records, function() {
contracts.push(this);
});

if (result.hasOwnProperty('offset')) {
getContracts(result.offset);
} else {
// I guess this is what you want
// If there is no offset property => resolve the promise
resolve('Your result goes here');
}
},
});
});
}

See the else block.

You can pass your final result (whatever you want to achieve after the completion of your task) inside the resolve. For example, you can create an array and append your result to that and at the end, you can pass that array inside resolve.

You can resolve this using .then() or async/await

async () => {
const result = await getContracts(offset);
};

or

getContracts(offset).then(result => { console.log(result) });

If you see some Unhandled Promise Rejection warning/error, you can always use try/catch block with async/await and .catch after .then.

EDIT:

  • First, you're not passing anything inside resolve. Whatever you pass inside the resolve will be reflected in .then(result).
  • Second, you have global variables and storing all your data inside them. So now you don't need to pass them inside the resolve but this is not a good approach because any function or the code outside can modify it. So I'll give you one example.
function getObjectContracts(offset) {
return new Promise((resolve, reject) => {
var data = {};

const objectContracts = [];

data['view'] = 'Alla Objektsavtal';

if (offset !== undefined) {
data['offset'] = offset;
}

$.ajax({
url: url + 'Objektsavtal',
headers: {
Authorization: apiKey,
},
data: data,
success: function(result) {
$.each(result.records, function() {
objectContracts.push(this);
});

if (result.hasOwnProperty('offset')) {
getObjectContracts(result.offset);
} else {
resolve(objectContracts);
}
},
});
});
}

Now, the other question is, how to resolve all these promises at once.

const finalFunction = async () => {
const [result1, result2, result3] = await Promise.all([
getObjectContracts(offset1),
getLandContracts(offset2),
getLocations(offset3),
]);

console.log(result1, result2, result3);
};

finalFunction();


Related Topics



Leave a reply



Submit