0

If I have a function with an ajax call, that gets some value from the database and then I need to send that value back to the php script and so on, how should I declare this variable ?

function call(id){
  $.ajax({
    . . .
    data: {id: id},
    success: function(res){
       window.id = res.id
     }
  });
}


//first call the function with a value
 call(10);

//then call it with the returned value every time the btn gets clicked

$('#btn').click(function(){
 call(window.id);
})

From my understanding is bad practice to use global variables like this because the global scope is already crowded, and it takes longer for the script to look up the variable. Can I define this value in a way that let's me send it back without being global ?

Uiot
  • 83
  • 8
  • 1
    Possible duplicate of [how to return the response from an asynchronous call](https://stackoverflow.com/questions/14220321/how-do-i-return-the-response-from-an-asynchronous-call) – Benjamin Gruenbaum Jan 12 '19 at 17:03

3 Answers3

4

The simplest way to avoid globals is to not work in the global scope in the first place. The way to do that is to create an IIFE (Immediately Invoked Function Expression), which is an anonymous function that is in the global scope, but since it's anonymous, it has no chance of colliding with another function with the same name. It's immediately invoked so that the code within it can execute (like an init kind of operation). Then, inside that function you do whatever you would normally do. This is where you would then create your IIFE scoped (not global scoped) variable.

// IIFE wrapper
(function(){

  let mainID = null; // IIFE scoped variable

  function call(id){
    $.ajax({
      . . .
      data: {id: id},
      success: function(res){
         mainID = res.id; // Set the IIFE scoped variable to the AJAX result
       }
    });
  }

  // Set up the event
  $('#btn').click(function(){
    all(res.id);  // <-- And, you can use it anywhere in the IIFE
  });

  call(10);        // first call the function with a value

})();  // <-- The extra set of parenthesis is what invokes the expression 

On a different note, your code won't work even with this in place because you are setting up the button's click event handler before the AJAX call has had a chance to complete. Just because that code comes after the async code, doesn't mean it will execute after the async code. The whole point of an async operation is that we don't know when it will complete and since JavaScript runs in a single-threaded environment, while the async operation is going about its business, the current function runs to completion. Only after the current thread is idle, will the results of the async operation (the success callback) be invoked. If the event handler relies on the result of the AJAX call, you'd need to set up the event handler inside the "success" callback, thus making the need for the global pointless anyway. You should still put all your code in an IIFE though as that is a best-practice.

Here's an example that shows a different type of async operation (a timer), which still operates like your AJAX call does that shows the problem:

// Global variable
var result = null;

// Start an async operation that will execute the callback
// after three seconds:
setTimeout(function(){
  result = "complete"
  console.log("Async operation " + result);
}, 3000);

// This function will run before the async operation 
// completes, so the use of the variable that is set
// by the async operation will fail
console.log("Local operation " + result);

So actually, you don't even need a global in the first place, you would just move the event binding code into the success callback. From a UI perspective, you may want the button to start out disabled and then in the success callback, enable it so that it can't be clicked until the click handler is set.

// IIFE wrapper
(function(){

  function call(id){
    $.ajax({
      . . .
      data: {id: id},
      success: function(res){
        // Set up the event now that the AJAX call is complete
        $('#btn').click(function(){
          call(res.id);
        });
       }
    });
  }

  call(10);        // first call the function with a value

})();  // <-- The extra set of parenthesis is what invokes the expression

Here's a working example of the whole thing:

(function(){
  // IIFE scoped variables
  let btn = document.querySelector("button");
  let random = null;

  function async(){
    // Async operation complete! 
    
    // Generate a random number that simulates the
    // DB response that would now be available
    random = Math.floor(Math.random() * 11);  
    
    // Enable the button:   
    btn.removeAttribute("disabled");
    
    // Set up the button's click event handler
    btn.onclick = function(){
      console.log("Button clicked. ID = " + random);
      async(); 
    };
    
  }
  
  // Start an async operation that will execute the callback
  // after three seconds:
  setTimeout(async, 3000);
})();
<button type="button" disabled>Click Me</button>
Scott Marcus
  • 64,069
  • 6
  • 49
  • 71
  • declare the `click` binding in the `success` callback is the good way to do it in my opinion, at least, you are sure that the user could click only if the context is already OK – Patrick Ferreira Jan 12 '19 at 17:02
  • @PatrickFerreira It's not just a good way, it's the only way. You simply can't use a result of an async operation until that operation completes and the only place you can be sure of that is in the success callback. – Scott Marcus Jan 12 '19 at 17:06
  • 1
    But if I set the event handler in the success call back, does that mean that it get's set every time the button is clicked, I'm not sure how that works, but isn't it a problem that the same event handler is set multiple times ? – Uiot Jan 12 '19 at 17:08
  • Since `call(id)` is called every time the `#btn` clicked not just once, you have a nice memory leak. – Kosh Jan 12 '19 at 17:14
  • @KoshVery It's not a memory leak. It's a recursive call. Why would you think it is? – Scott Marcus Jan 12 '19 at 17:19
  • @Uiot I didn't notice that you were calling `call` again inside the button's `click` handler, so yes, each time you click the button, you would be calling the AJAX operation again and the button's handler would be set to the new callback. Is that what you want? What are you trying to achieve? – Scott Marcus Jan 12 '19 at 17:20
  • @ScottMarcus Yup, I need to call the ajax function with the last value returned from the database every time the button is clicked. – Uiot Jan 12 '19 at 17:23
  • 1
    @ScottMarcus, if you want to set an event listener within the callback, you need to detach the previous event listener. Or you will call `call()` function several times with all the `id` previously attached – Kosh Jan 12 '19 at 17:23
  • @ScottMarcus, OP uses jQuery `.click` which is a shorthand for `.on('click', function)` – Kosh Jan 12 '19 at 17:26
  • @KoshVery Ok yes, but in my answer, I'm not using JQuery. – Scott Marcus Jan 12 '19 at 17:36
  • @Uiot See my updated answer which simulates your use case to show how it would work. – Scott Marcus Jan 12 '19 at 17:37
  • @ScottMarcus, you do in you code snippet #3. And OP does as well. And [this is what i mean](https://jsfiddle.net/pmz1tbwL/) -- look into console. – Kosh Jan 12 '19 at 17:39
  • @KoshVery [Sigh] Yes, and that was an example. In my executable examples of how to actually code the solution, I don't. – Scott Marcus Jan 12 '19 at 17:40
  • @ScottMarcus So, I just need to detach my event listener every time in the success callback or not use jquery for this, right ? – Uiot Jan 12 '19 at 18:04
  • Would it hurt if in the success callback I use something like 'var success = true' and then make a check on the button click `if(success == true)` so I don't have to set the event listener in the success callback since I have more than one .. ? – Uiot Jan 12 '19 at 19:12
  • 1
    @Uiot If you use the standard `onclick` property instead of JQuery `.click`, the old handler will be replaced with the new one. Yes you could do the success check you mentioned, but it seems like you said earlier that you do want a new event handler registered upon each click. In that case, you wouldn't do that. – Scott Marcus Jan 12 '19 at 21:02
0

As the id is semantically tied to the #btn, you might store the returned id value as the #btn property:

function call(id){
  $.ajax({
    . . .
    data: {id: id},
    success: function(res){
       $('#btn').prop('response_id', res.id);
     }
  });
}

//first call the function with a value
 call(10);

//then call it with the returned value every time the btn gets clicked

$('#btn').click(function(){
 call(this.prop('response_id'));
})
Kosh
  • 16,966
  • 2
  • 19
  • 34
  • While this *can* work, buttons don't have a `response_id` property. And, so the solution is a bit "hack-ish". If you wanted to go this way, the correct approach would be to store the information in a [`data-*` attribute](https://developer.mozilla.org/en-US/docs/Learn/HTML/Howto/Use_data_attributes), which was created for just this kind of thing. Better yet, don't put any code in the global scope and use an IIFE. See my answer. – Scott Marcus Jan 12 '19 at 16:37
  • This is still a global variable - just on the DOM. – Benjamin Gruenbaum Jan 12 '19 at 17:02
  • @ScottMarcus, thank you for your comment. But any `HTMLElement` is an `Object` in the first place, so one can set any property. Using a `data-` attribute is almost the same approach, but I don't like it due its implementation. Regarding IIFE, you're absolutely right -- it's a better approach (if applicable). – Kosh Jan 12 '19 at 17:08
  • @KoshVery That's why I said your suggestion *could* work, but adding custom properties to DOM objects is an anti-pattern and the reason `data-*` attributes were added. I don't know what problem you have with the implementation. They are very easy to work with and (as I said), if you were going to go down this road, they are the correct approach to use. – Scott Marcus Jan 12 '19 at 17:10
-3

Just replace window.id with call.id (that is, store it as a property of your function):

function call(id){
  $.ajax({
    . . .
    data: {id: id},
    success: function(res){
       call.id = res.id
     }
  });
}


//first call the function with a value
call(10);

//then call it with the returned value every time the btn gets clicke
$('#btn').click(function(){
  call(call.id);
})
Victor
  • 5,493
  • 1
  • 27
  • 28