Reputation: 150
I am trying to 'fill' a select in a form, based on a stored (array) value: city_selector_vars
. This stored value shows the correct values (see code below).
I loop through this array and try to get the the cities (get_states) for the defined stateCode. I pass stateCode as a value into the function.
If I log state_data.country_code
before $.post
I get the expected return. If I log state_data.country_code
inside $.post
it's not always the same as the passed country_code. I think it has something to do with scope but my knowledge is not good enough to figure it out.
/**
* city_selector_vars
* [
* 'countryCode' => 'NL',
* 'stateCode' => 'NL-FL',
* 'cityName' => 'A name'
* ]
* [
* 'countryCode' => 'BE',
* 'stateCode' => 'BE-BR',
* 'cityName' => 'Another name'
* ]
*/
if ( true === Array.isArray(city_selector_vars) ) {
for (i = 0; i < city_selector_vars.length; i++ ) {
get_states(city_selector_vars[i].countryCode, (response)=> {
// console.log(response);
});
}
}
function get_states(countryCode, callback) {
const state_data = {
action: 'get_states_call',
country_code: countryCode
};
// console.log(state_data.country_code) shows correct country code
$.post(ajaxurl, state_data, (response)=> {
// here the wrong response first shows
// state_data.country_code is not always the same as state_data.country_code
callback(response);
});
}
function get_states_call( $country_code = false ) {
// $country_code = always false, even though it is passed
if ( false == $country_code ) {
if ( isset( $_POST[ 'country_code' ] ) ) {
// here it sometimes picks up the incorrect value
$country_code = $_POST[ 'country_code' ];
}
}
// code removed, since depends on code above
$items = [];
echo json_encode( $items );
die();
}
Because it only sometimes picks up the wrong value, I think it has to do with scope, but I don't know enough to pinpoint it.
Upvotes: 0
Views: 322
Reputation: 589
As to the admin_post_edit_load_states
function, I think Promise.all is the correct approach:
function admin_post_edit_load_states() {
if (true === Array.isArray(city_selector_vars)) {
// preparing the response array
const response_states = []
for (i = 0; i < city_selector_vars.length; i++) {
// try - catch to handle errors
try {
// await the response
const d = get_states(city_selector_vars[i].countryCode);
// add response to the response array
response_states.push(d)
} catch (err) {
// handle error
console.log(err)
}
}
console.log(response_states);
Promise.all(response_states).then(function(jsonResults) {
var instance_count = 0;
for (i = 0; i < jsonResults.length; i++) {
var obj = JSON.parse(jsonResults[i]);
var len = obj.length;
var $stateValues = '';
var select_state = $('select[name*="row-' + instance_count + '"][name*="stateCode"]');
var stored_state = city_selector_vars[instance_count].stateCode;
select_state.fadeIn();
for (j = 0; j < len; j++) {
$selected = '';
var state = obj[j];
var current_state = state.country_code + '-' + state.state_code;
if (current_state === stored_state) {
$selected = ' selected="selected"';
}
var selected = $selected;
$stateValues += '<option value="' + state.country_code + '-' + state.state_code + '"' + selected + '>' + state.state_name + '</option>';
}
select_state.append($stateValues);
instance_count++;
}
});
}
}
This should keep the original order while being asynchronous. Notice what we are doing here:
First we fill the array response_states
with promises (each call to get_states returns a promise). Then, Promise.all(response_states)
creates a new promise that will be resolved when all the promises in the response_states array are resolved. Finally, the parameter jsonResults is an array containing the corresponding value for each resolved promise in the array response_states
.
Let's see if it works.
Upvotes: 0
Reputation: 8329
From your code it seems that you are implementing a function to query Wordpress data.
I don't think your problem is one of variable scope - it's rather a problem of async functions. You send out one AJAX request per country - every request travels to the server "individually", get processed by the server, and the response comes back. Nothing ensures that the response arrives in the same order as the requests were sent out.
Consider this:
const arr = []
function getSingleItem(id) {
fetch(`https://jsonplaceholder.typicode.com/todos/${id}`)
.then(response => response.json())
.then(json => {
console.log(json.id)
})
}
function getAllItems() {
for (let i = 1; i < 10; i++) {
getSingleItem(i)
}
}
getAllItems()
You can see in the console, that the responses do not arrive in the order the requests were sent (json.id
is actually a running number from 1 to i
- they should be in order).
You have to take care of ordering yourself:
const arr = []
function getSingleItem(id) {
return fetch(`https://jsonplaceholder.typicode.com/todos/${id}`)
.then(response => response.json())
.then(json => {
return json.id
})
}
async function getAllItems() {
for (let i = 1; i < 10; i++) {
const r = await getSingleItem(i)
arr.push(r)
}
console.log(arr)
}
getAllItems()
By setting up an async await
in the functions I can be sure, that the responses show up in the order the requests were sent out. (await
does not "let" the for()
loop to go further until the awaited response does not arrive - kinda' like synchronous code.)
In this case I use that the fetch()
function returns a Promise
object - an object that can be awaited in my other function.
Your Javascript code can be modified:
/**
* city_selector_vars
* [
* 'countryCode' => 'NL',
* 'stateCode' => 'NL-FL',
* 'cityName' => 'A name'
* ]
* [
* 'countryCode' => 'BE',
* 'stateCode' => 'BE-BR',
* 'cityName' => 'Another name'
* ]
*/
async function get_all_state(city_selector_vars) {
if (true === Array.isArray(city_selector_vars)) {
// preparing the response array
const response_states = []
for (i = 0; i < city_selector_vars.length; i++) {
// try - catch to handle errors
try {
// await the response
const d = await get_states(city_selector_vars[i].countryCode);
// add response to the response array
response_states.push(d)
} catch (err) {
// handle error
console.log(err)
}
}
// return the array - in order!
return response_states
}
}
function get_states(countryCode, callback) {
const state_data = {
action: 'get_states_call',
country_code: countryCode
};
// console.log(state_data.country_code) shows correct country code
// returning a Promise object, so await works in the other function
return new Promise((resolve, reject) => {
$.post(ajaxurl, state_data, (response) => {
// here the wrong response first shows
// state_data.country_code is not always the same as state_data.country_code
// resolving the Promise when the response arrives
resolve(response)
});
})
}
In your modified code a Promise
had to be explicitly created (remember - fetch()
returns a Promise
?), so it can be handled by async-await.
I think the snippet should work as it is, but if not, then only a small debugging is needed :)
If you send JSON with Wordpress, it's advised to use the wp_send_json()
function.
More: https://developer.wordpress.org/reference/functions/wp_send_json/
Upvotes: 1