Ok, so I've read 1,000,000+ articles on jQuery deferreds and/or promises, and I'm still getting something wrong.
functionOne() {
var deferred = $.Deferred();
var request = $.ajax({
url: 'http://example.com/mypath/etc'
});
request.done(function(data) {
// TODO: I've got stuff here that takes a while.
deferred.resolve();
});
return deferred.promise();
}
functionTwo() {
// Something that depends on the others being finished.
}
$.when(
functionOne(),
anotherLongRunningFunctionWithAjax())
.then(
functionTwo()
);
I need any function(s) in the "when" to fully complete (.ajax done) before the "then" starts. However, the promise returns immediately (as expected), but functionTwo starts, even though functionOne has not called "done".
I'm sure it's a fundamental misunderstanding of deferred and the chain-of-calls.
Edit:
function functionOne() {
console.log('functionOne called');
var request = $.ajax({
url: 'http://example.com/mypath/etc'
});
request.done(function(data) {
console.log('Starting Done.');
setTimeout(function () {
console.log('Done finished.');
}, 5000);
});
console.log('Returning promise.');
return request;
}
function functionTwo() {
console.log('functionTwo called');
}
$.when(functionOne()).then(functionTwo());
Gives me this in the console:
functionOne called
Returning promise.
functionTwo called (should be called after Done is finished.)
Starting Done.
Done finished.
Taking the code in your edit, there are two issues:
The timer in functionOne starts after request is resolved, yet you return request. So whatever happens with the timer... it is of not relevance to the returned promise, which at that time is already resolved.
You call functionTwo immediately, instead of passing the function reference for the $.when promise to call back
Here is working code:
function functionOne() {
console.log('functionOne called');
console.log('Returning promise.');
return $.ajax({
url: 'https://jsonplaceholder.typicode.com/posts/1'
}).then(function(data) {
console.log('Starting Done.');
var dfd = $.Deferred();
setTimeout(function () {
console.log('Done finished.');
dfd.resolve(data); // indicate when you are done
}, 2000); // 2 seconds will do ;-)
return dfd.promise(); // you need to return a promise again
});
}
function functionTwo() {
console.log('functionTwo called');
}
// don't call functionTwo immediately, leave that to the promise to do:
$.when(functionOne()).then(functionTwo);
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
you are using an anti-pattern since $.ajax itself returns a promise
Just do
functionOne() {
var request = $.ajax({
url: 'http://example.com/mypath/etc'
});
request.done(function(data) {
// TODO: I've got stuff here that takes a while.
});
return request
}
Related
I have a simple JS function defined like this :
function firstFunction() {
$.ajax({
url: "/path/to/my/endpoint",
type: "GET"
}).done(function (data) {
localStorage.setItem("myItem", data);
});
}
Later on, I have another function defined like this :
function mySecondFunction() {
if(localStorage.getItem("myItem") == null) {
// Here I want to call firstFunction() and stop everything until it finishes
}
//Immediately use localStorage.getItem("myItem") for other purposes
//no matter I entered the if() or not
}
With a simple async: false in $.ajax, it works, but I've seen it's going to be deprecated and I want to avoid this solution.
Could you please suggest how to wait for mySecondFunction when entering my if() ?
I tried with $.when() but without success, maybe I did somehting wrong ?
I tried something like
function mySecondFunction() {
var deferred = $.Deferred();
if(localStorage.getItem("myItem") == null) {
$.when(firstFunction()).then(function () {
deferred.resolve();
})
}
else {
deferred.resolve();
}
//other instructions
}
But other instructions are called BEFORE the end of firstFunction()
Make firstFunction() return a promise.
function firstFunction() {
return new Promise((res, err) => {
$.ajax({
url: "/path/to/my/endpoint",
type: "GET"
}).done(function (data) {
localStorage.setItem("myItem", data);
res()
});
});
}
Make mySecondFunction aysnc.
async function mySecondFunction() {
if(localStorage.getItem("myItem") == null) {
await firstFunction();
}
localStorage.getItem("myItem")
...
}
This is how I would recommend you do this, since the ajax request wont block execution of other code, like button callbacks. Async/await and promises are hard to grasp at first, so here's some reading on how they work behind the scenes.
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function
Just change your if clauses with a while loop and call your firstFunction in that loop.
Example:
function mySecondFunction() {
while(localStorage.getItem("myItem") == null) {
firstFunction()
}
}
I know this is a misunderstanding on my part and i'm trying to learn what i'm doing wrong and I could really use some help please.
I have an ajax request that is succeeding and returning data just fine. The Api returns an error code, then I try to return a response based on what code I was given, except that the function is only returning what the ajax call delivered.
function getData(){
var promise = $.ajax({
url: 'api.php',
type: 'POST',
dataType: 'json'
});
return promise.done(function(data){
console.log(data);
if(data.errorCode === 0){
return data;
} else {
return 'failed';
}
});
}
$(document).ready(function(){
$('.btn').click(function(){
var apiData = getData();
console.log(apiData);
});
});
So if the api returns an error code that is not 0 then the function getData should return a string of 'failed', except it returns the data element. I know that the code is not 0 because the console.log shows that the code is 999. What am I doing wrong? Why I can't I get it to return my string of "failed" ?
getData doesn't (and can't) return the data or "failed"; it returns a promise. You consume that promise much the way you did inside getData:
$(document).ready(function(){
$('.btn').click(function(){
getData().done(function(apiData) { // **
console.log(apiData); // **
}); // **
});
});
In that callback, apiData will be whatever your callback in getData returned, so it'll be the data object or the string "failed".
(I've used done there because it's what you used elsewhere, but normally I'd use then, the standard promise function.)
One of the key things to understand about promises is that then (and done) returns a new promise (technically, with real promises, a thenable) that will be settled based on what the callback does.
So consider (let's stick with jQuery's Deferred for now):
function doSomething() {
var d = $.Deferred();
setTimeout(function() {
// Resolve our promise with "a"
d.resolve("a");
}, 10);
return d.promise();
}
// Consume the promise and put it through a chain:
doSomething()
.then(function(result) {
// This callback happens to do synchronous processing
console.log("First callback got", result);
return result.toUpperCase();
})
.then(function(result) {
// This one does something async, and so it returns a promise
var cd = $.Deferred();
setTimeout(function() {
console.log("Second callback got", result);
cd.resolve(result + result);
}, 10);
return cd.promise();
})
.then(function(result) {
console.log("Third callback got", result);
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
The output of that is
First callback got a
Second callback got A
Third callback got AA
Now, earlier I said then always returns a promise (thenable). How does it do that when my first callback is synchronous and returns a value directly, but my second one is async and returns a promise? The then function looks at the return value from the callback and, if it's "thenable" (something with then on it), returns it; if it's not thenable, it creates a new, resolved promise with the value as the resolution value.
Just for completeness, here's the example above with native JavaScript promises (requires browser support):
function doSomething() {
return new Promise(function(resolve) {
setTimeout(function() {
// Resolve our promise with "a"
resolve("a");
}, 10);
});
}
// Consume the promise and put it through a chain:
doSomething()
.then(function(result) {
// This callback happens to do synchronous processing
console.log("First callback got", result);
return result.toUpperCase();
})
.then(function(result) {
// This one does something async, and so it returns a promise
return new Promise(function(resolve) {
setTimeout(function() {
console.log("Second callback got", result);
resolve(result + result);
}, 10);
});
})
.then(function(result) {
console.log("Third callback got", result);
});
The output of that is
First callback got a
Second callback got A
Third callback got AA
According to http://api.jquery.com/jquery.ajax/ documentation, you can either chain .done, .fail, .always or .then.
$(document).ready(function() {
$('.btn').click(function() {
$.ajax({
url: 'api.php',
type: 'POST',
dataType: 'json'
})
.done(function(data) {
console.log(data);
if (data.errorCode === 0) {
console.log(data)
} else {
console.log(data)
}
});
});
});
This will work
I was reading about promises and found this fiddle created by the author of this post
The code is here:
var def, getData, updateUI, resolvePromise;
// The Promise and handler
def = new $.Deferred();
updateUI = function (data) {
$('p').html('I got the data!');
$('div').html(data);
};
getData = $.ajax({
url: '/echo/html/',
data: {
html: 'testhtml',
delay: 3
},
type: 'post'
})
.done(function(resp) {
return resp;
})
.fail(function (error) {
throw new Error("Error getting the data");
});
// Event Handler
resolvePromise = function (ev) {
ev.preventDefault();
def.resolve(ev.type, this);
return def.promise();
};
// Bind the Event
$(document).on('click', 'button', resolvePromise);
def.then(function() {
return getData;
})
.then(function(data) {
updateUI(data);
})
.done(function(promiseValue, el) {
console.log('The promise was resolved by: ', promiseValue, ' on ', el);
});
// Console output: The promise was resolved by: click on <button> </button>
I do understand from the first part of this series that a deferred has a promise which can be exposed using the promise method on it.
Promises have then method which returns a promise for chaining.
Here they resolve the promise on the deferred in resolvePromise,then the then method on the deferred which I dont think is a promise is executed.What am I missing here?
Deferred objects in jQuery are also thenables and you can use them in place of promises. Doing so is rather uncommon though.
var d = $.Deferred().resolve();
d.then(function(){
console.log("HI"); // this will run.
});
The original $.ajax having .done and .fail is pointless in this case, especially the .done whose return value is ignored and has no impact.
In all honestly, I think the code could be improved to something like rather easily:
var getData = $.post('/echo/html/', { html: 'testhtml', delay: 3 });
var d = $.Deferred();
$(document).on('click', 'button', function(ev){
d.resolve();
return false;
});
$.when(d, getData).then(function(_, data){
$('p').html('I got the data!');
$('div').html(data);
});
There is no point in .thening if you only use the identity function (that is, return the same thing and do nothing else.
There is no point in .doneing only to return the same thing.
Generally, I would advise against promises for handing events unless the events are strictly one time.
I'm having a problem with callback functions in javascript. What I want to do is: loop on a for and call a function passing i as parameter. With that in mind, I have to loop to the next interaction only after the previous one has been finished. I don't know if this is a problem but inside the function I'm sending i as parameter, I have another callback function. Here is my code:
for(i=0; i<10; i++) {
aux(i, function(success) {
/*
* this should be made interaction by interaction
* but what happens is: while I'm still running my first interaction
* (i=0), the code loops for i=1, i=2, etc. before the response of
* the previous interaction
*/
if(!success)
doSomething();
else
doSomethingElse();
});
}
function aux(i, success) {
... //here I make my logic with "i" sent as parameter
getReturnFromAjax(function(response) {
if(response)
return success(true);
else
return success(false);
});
});
function getReturnFromAjax(callback) {
...
$.ajax({
url: myUrl,
type: "POST",
success: function (response) {
return callback(response);
}
});
}
jQuery's Deferred can be a bit tricky to get right. What you'll have to do is stack your promises in a chain. For example:
var
// create a deferred object
dfd = $.Deferred(),
// get the promise
promise = dfd.promise(),
// the loop variable
i
;
for(i = 0; i < 10; i += 1) {
// use `then` and use the new promise for next itteration
promise = promise.then(
// prepare the function to be called, but don't execute it!
// (see docs for .bind)
aux.bind(null, i, function(success) {
success ? doSomethingElse() : doSomething();
})
);
}
// resolve the deferred object
dfd.resolve();
for this to work, aux must also return a promise, but $.ajax already does this, so just pass it through and everything should work:
in aux:
function aux(i, callback) {
console.log('executing for `aux` with', i);
// return the ajax-promise
return getReturnFromAjax(function(response) {
callback(Boolean(response));
});
}
in getReturnFromAjax:
function getReturnFromAjax(callback) {
// return the ajax-promise
return $.ajax({
url: '%your-url%',
type: '%method%',
success: function (response) {
callback(response);
}
});
}
demo: http://jsbin.com/pilebofi/2/
I'd suggest that you'd look into jQuery's Deferred Objects and jQuery.Deferred()-method instead of making your own callback queue functions (as you are already using jQuery anyway).
Description: A constructor function that returns a chainable utility
object with methods to register multiple callbacks into callback
queues, invoke callback queues, and relay the success or failure state
of any synchronous or asynchronous function.
I don't have experience with jQuery, but your callback looks a bit fishy to me.
In plain JS I'd suggest trying something among the lines of this:
function yourMainFunction
{
function callbackHandler(result)
{
// Code that depends on on the result of the callback
}
getAjaxResults(callbackHandler);
}
function getAjaxResults(callbackHandler)
{
// Create xmlHttpRequest Handler, etc.
// Make your AJAX request
xmlHttp.onreadystatechange = function()
{
if (xmlHttp.readyState == 4 && xmlHttp.status==200)
{
// Do stuff you want to do if the request was successful
// Define a variable with the value(s) you want to return to the main function
callbackHandler(yourReturnVariable);
}
}
}
I have a function. The inside of the function looks like:
if (isNewCustomer) {
doSomething();
cleanup();
}
else {
$.getJSON(..., function(result) {
doSomethingElse();
cleanup();
});
}
I was hoping I could simply this by using deferred. My attempt looks like:
var do_it = doSomething;
if (!isNewCustomer) {
do_it = $.getJSON(..., function(result) {
doSomethingElse();
});
}
$.when(do_it).done(function() {
cleanup();
});
But this isn't working. What am I doing wrong?
EDIT: Renaming variable do to do_it. This isn't the problem with the code. The problem is that when do_it is doSomething, doSomething doesn't get executed.
do is a keyword in javascript, so better rename the variable.
var do_it = doSomething;
if (!isNewCustomer) {
do_it = $.getJSON(..., function(result) {
doSomethingElse();
});
}
// || << note me
$.when(do_it()).done(function() {
cleanup();
});
var result;
if (isNewCustomer) {
result = doSomething();
} else {
result = $.getJSON( ..., function( data ) {
doSomethingElse( data );
});
}
$.when( result ).done(function() {
cleanup();
});
See the code above: you never called the function just like Gigi pointed out.
Check out this jsfiddle
https://jsfiddle.net/timlint/tg7xqtha/
Using Deferred is the way to go. It's a little hard to grasp the flow sometimes and how to pass data around but this example may give you some insight.
You can almost think of a deferred as a flag. in a function you create a deferred object.
the function returns the .promise() for that deferred. this allows you to call the function doSomething(bool).done() and do something once it finishes. You resolve the deferred when you know the task is complete and it won't be called until then.
function doSomething(isNewCustomer)
{
// think of a deferred as a flag object in a way
var d = $.Deferred();
if(!isNewCustomer)
{
$.getJSON(..., function(result) {
// work with data
}).done(function() {
// finisn up data
// set deferred to resolved
d.resolve();
});
}
else
{
// some business logic
// set deferred to resolved
d.resolve();
}
// returning the promise lets you call .done()
// on this function call in the main call
return d.promise();
}
You need an explicit Deferred. If you pass when() an argument that is not a Deferred, the function is invoked immediately, and is probably why you're getting unexpected results.
var deferred = $.Deferred();
if (isNewCustomer) {
deferred.resolveWith(doSomething());
}
else {
$.getJSON(...).
done(function(result) {
deferred.resolveWith(doSomethingElse(result));
}).
fail(function(...) {
deferred.rejectWith(...);
});
}
deferred.promise().always(function() { cleanup(); });