0

Ive been struggling for a while to get this code to work properly. Basically im hitting an api to get values and add them to a table. My great idea was to take the parameter im sending from the response to the api call and group them together each time in an array so they dont get mixed up. the Ive attempted async:false, different varieties of using promises inside a loop and outside.

Can anyone please crack this nut for me so i can be a better programmer? i have a simplified version of the code here (please note i have used some things for examples):

var slotId = [];
var status = [];
var slotFromAjax =[];
var UnAuthIssue;
var AuthIssue;
var AuthStep;
var UnAuthStep;
var TLD;
var mySpace;
var authStepColour;
var authIssueColour;
var unAuthStepColour;
var unAuthIssueColour;
var url;
var slotNumber;
var messages2 = [92578, 95157];



if (document.getElementById("tickets_list_whlist")) {
  var table = document.getElementById("tickets_list_whlist");
} else {
  console.log('no table');
}

function makeHttpRequest(messages2) {
 
  return new Promise((resolve, reject) => {
    var promises = [];
    for (var k = 0; k < messages2.length; k++) {
      promises.push(
        new Promise((resolve, reject) => {
         
            GM.xmlHttpRequest({
            method: "GET",
            url:
             "example.com/api/"+messages2[k],
            headers: {
              "Content-Type": "application/json",
              "Origin": "https://example.com", // replace with your domain
            },
            onload: (response) => {
              if (response.status == 200) {
                var NewJson = JSON.parse(response.responseText);
                if (NewJson.collection[0].schemas[1] !== undefined) {
                  slotNumber = NewJson.collection[0].slot;
                  if (NewJson.collection[0].schemas[0].workflow == "Yes") {
                    AuthStep = NewJson.collection[0].schemas[0].step;
                    UnAuthStep = NewJson.collection[0].schemas[1].step;
                    AuthIssue = NewJson.collection[0].schemas[0].issue;
                    UnAuthIssue = NewJson.collection[0].schemas[1].issue;
                  }

                  if (AuthStep) {
                    if (
                      AuthStep == "Complete"
             
                    ) {
                      if (UnAuthIssue === undefined) {
                        UnAuthIssue = "No issue";
                      } else if (
                        AuthIssue == "" ||
                        AuthIssue == null ||
                        AuthIssue == ""
                      ) {
                        AuthIssue = "No Issue";
                        authStepColour = "green";
                        authIssueColour = "black";
                      }

                               slotFromAjax.push([slotNumber, AuthStep]);
                    }
                  }
                }
                resolve(response);
              } else {
                reject(response);
              }
            },
            onerror: (error) => {
              reject(error);
            },
          });
        })
      );
    }
    Promise.all(promises)
      .then(() => {
        resolve(slotFromAjax);
      })
      .catch((error) => {
        reject(error);
      });
  });
}

function firstFunction() {
  console.log("Running first function");
  return makeHttpRequest(messages2);
}

function secondFunction(data) {
  // This function runs second and depends on the data returned by the HTTP request
  console.log("Running second function with data:", data);
  return Promise.resolve();
}

function thirdFunction() {
  console.log("Running third function");
}

// Call the functions in order using Promises
firstFunction()
  .then((data) => {
    return secondFunction(data);
  })
  .then(() => {
    thirdFunction();
  })
  .catch((error) => {
    console.error(error);
    console.log("Error outer promise");
  });
luk2302
  • 55,258
  • 23
  • 97
  • 137
Bangorsteve
  • 19
  • 1
  • 6
  • better use axios lib, https://axios-http.com/ – hamaronooo Mar 13 '23 at 09:36
  • 2
    [Don't use the explicit promise antipattern](https://stackoverflow.com/questions/23803743/what-is-the-explicit-promise-construction-antipattern-and-how-do-i-avoid-it). – Quentin Mar 13 '23 at 09:41
  • 3
    This would be a lot easier if you used `fetch` (which returns a promise natively) than your current approach of manually wrapping `XMLHttpRequest` in a promise. – Quentin Mar 13 '23 at 09:41
  • 1
    `"Content-Type": "application/json",` — GET requests are not allowed request bodies. Claiming you have a JSON request body is a lie. This makes a request preflighted if it is cross origin which will only cause you problems. – Quentin Mar 13 '23 at 09:42
  • 1
    `"Origin": "https://example.com", // replace with your domain` — The browser sets the Origin header automatically for cross origin requests. You **can't** override it. This does nothing. – Quentin Mar 13 '23 at 09:42
  • 1
    It's really unclear what where you want this to wait where it isn't already. – Quentin Mar 13 '23 at 09:44
  • Yes Quentin, i was getting a cors error with fetch so moved to the http request – Bangorsteve Mar 13 '23 at 09:45
  • The seconFunction is supposed to wait but it doesnt and my array isnt populated. – Bangorsteve Mar 13 '23 at 09:47
  • the `GM_xmlhttpRequest` function from the Greasemonkey API provides access to the chrome-privileged XMLHttpRequest object. This means that it is possible to issue requests to domains other than that of the current page without CORS issues – Endless Mar 13 '23 at 09:48
  • @Quentin there is a difference here since OP is using `GM.xmlHttpRequest` which is a userscript manager provided API. When using it, CORS is avoided, while `fetch()` will still issue the request from the page and thus still be subject to CORS restrictions. – VLAZ Mar 13 '23 at 09:49
  • @Endless `GM.xmlHttpRequest` is the Greasemonkey one, `GM_xmlhttpRequest` is Tampermonkey. And I forget which of the two Violentmonkey uses. The API is in a bit of a state... – VLAZ Mar 13 '23 at 09:51
  • Side note: Is it really intended that when the current response has no "workflow", that the `AuthStep` from the **previous iteration** is used?? This seems wrong. – trincot Mar 13 '23 at 10:17
  • There seem to be quite a few variables that are assigned values, but then never used (`AuthIssue`, `UnAuthIssue`, `UnAuthStep`, `authStepColour`, `authIssueColour`...): a lot of hassle for no purpose. And why do you declare almost all variables as globals? – trincot Mar 13 '23 at 10:24
  • Are you saying `slotFromAjax.push` is executing **after** `secondFunction` executes? Can you be clear about that? Please provide console output that proves the order of execution and how that is different from what you expect. – trincot Mar 13 '23 at 10:41
  • Hi trincot, i can post you the entire code if you wish but this is just an example snippet to show what i am aiming for. So i get asset numbers and find out their status and then add the status to a table by looping the table. I need to store these in an array and then i search the table for the asset number to know where to post a new cell in the table to show that row's status. – Bangorsteve Mar 13 '23 at 12:48
  • WHat happens in my original code is the code is attempting to populate the new cell in the table with data but there are blank cells because the xhr isnt fully finished before it starts populating. – Bangorsteve Mar 13 '23 at 12:58
  • Can anyone post an example of this working please? Looping a http request and waiting til its finished and displaying the results. – Bangorsteve Mar 13 '23 at 13:26

1 Answers1

0

If I could switch it up a bit then i would have done this like so:

const slotFromAjax = []
const ids = [92578, 95157]

const table = document.getElementById('tickets_list_whlist') ?? console.log('no table')

/** @type {fetch} */
function ittyFetch(url, init = {}) {
  return new Promise((rs, rj) => {
    const xhr = GM.xmlHttpRequest({
      method: init.method || 'GET',
      url,
      responseType: 'blob',
      headers: Object.fromEntries(new Headers(init?.headers).entries()),
      data: init.body || '',
      onload (result) {
        rs(new Response(result.response, {
          status: result.status,
          statusText: result.statusText,
          headers: result.responseHeaders
        }))
      },
      onabort: () => rj(new DOMException('Aborted', 'AbortError')),
      ontimeout: () => rj(new TypeError('Network request failed, timeout')),
      onerror: (err) => rj(new TypeError('Failed to fetch: ' + err.finalUrl))
    })
  })
}

async function makeHttpRequest (id) {
  const res = await ittyFetch(`example.com/api/${id}`, {
    headers: {
      'Content-Type': 'application/json',
      'Origin': 'https://example.com', // replace with your domain
    },
  })
  
  if (!res.ok) throw new Error(`HTTP error! status: ${res.status}`)

  const newJson = await res.json()

  if (newJson.collection[0].schemas[1] !== undefined) {
    const slotNumber = newJson.collection[0].slot
    const [ collection ] = newJson.collection

    if (collection.schemas[0].workflow === 'Yes') {
      let authStep = collection.schemas[0].step
      // let unAuthStep = collection.schemas[1].step
      let authIssue = collection.schemas[0].issue
      let unAuthIssue = collection.schemas[1].issue
      
      if (authStep == 'Complete') {
        if (unAuthIssue == undefined) {
          unAuthIssue = 'No issue';
        } else if (!authIssue) {
          authIssue = 'No Issue'
          // let authStepColor = 'green'
          // let authIssueColor = 'black'
        }
  
        slotFromAjax.push([slotNumber, authStep])
      }
    }

  }
}

async function firstFunction() {
  console.log('Running first function')
  
  // Do one request at a time
  // 
  // const results = []
  // for (const id of ids) results.push(await makeHttpRequest(id))
  
  // Do all request at once (parallel)
  return Promise.all(ids.map(makeHttpRequest))
}

async function secondFunction(data) {
  // This function runs second and depends on the data returned by the HTTP request
  console.log('Running second function with data:', data)
}

async function thirdFunction() {
  console.log('Running third function')
}

firstFunction()
  .then(secondFunction)
  .then(thirdFunction)
  .catch(console.error)

a bit untested...

Endless
  • 34,080
  • 13
  • 108
  • 131
  • Thanks for this. Does your fetch loop or is it just called as per the function? Ive tried taking the loop out of the http request function and putting it in its own function but i still get the same results. I need to loop over maybe 20 or 30 numbers with the http request. – Bangorsteve Mar 13 '23 at 12:46
  • 1
    my `makeHttpRequest` function only do one request at a time, the `firstFunction` function makes it so that it runs multiple request in parallel, you can change it for a await instead – Endless Mar 13 '23 at 13:26