0

I can't seem to find how to solve my problem.

I am using socket to get live data, there is a .on() event which then executes another function async to get api data.

socket.on('data', (data) => { // listen to live events - event 1, 2, 3, 4, 5
  getAPIData(data);
});

async function getAPIData(data){
 const {symbol} = data;
 axios.get(`https://api.nasa.gov/planetary/apod?name=${symbol}`).then((newData)=>{
  console.log(newData); // console events  - 1,3,5,2,4 - I need this to be in the right order as they come in
 })
};

Socket.on() get multiple events per second and I need each for calculations. my problem now is that console.log(newData) is not logging in order as they come in because its promise based async.

and I really need the exact order they come in, how to do this in JavaScript nodejs.

T.J. Crowder
  • 1,031,962
  • 187
  • 1,923
  • 1,875
Mayga Fatmawati
  • 101
  • 2
  • 7
  • Does this answer your question? [How do I execute four async functions in order?](https://stackoverflow.com/questions/57262317/how-do-i-execute-four-async-functions-in-order) – EzioMercer Mar 07 '22 at 09:20
  • There's no reason for `getAPIData` to be async. You're using `then` not `await`. – Andy Mar 07 '22 at 09:20
  • @EzioMercer - I think probably not, if we look closely at what the OP is trying to do. – T.J. Crowder Mar 07 '22 at 09:22
  • Are you sure you want a reasonable thing here, or do you just want your console logs to be "ordered" because it looks neat? I would rather have things be carried out as fast as possible, instead of introducing artificial waiting times so the console output looks better. – Tomalak Mar 07 '22 at 09:22
  • Thank you for the answer, I made a mistake of using a wrong example. I have added my actual code now. – Mayga Fatmawati Mar 07 '22 at 10:53
  • @EzioMercer I know how to use promiseAll and that's not the solution I am looking for. My code executes the same function every time we get new log event. so basically promiseAll will never solve. – Mayga Fatmawati Mar 07 '22 at 10:56
  • @Tomalak I added my actual code now. I don't want to console the data, I need to first check if I am interested in the data (if exists in database) then continue to next function (calculations and saving) – Mayga Fatmawati Mar 07 '22 at 11:00

1 Answers1

1

You can serialize the calls to axios and the logging of the results like this (but keep reading, we can also overlap the axios calls while still doing the output in order):

let queueLength = 0;
let sequencer = Promise.resolve();
function getAPIData(data){
    const {symbol} = data;
    ++queueLength;
    console.log("Added work item, queue length: " + queueLength);
    sequencer = sequencer
        .then(() => axios.get(`https://api.nasa.gov/planetary/apod?name=${symbol}`))
        .then((newData) => {
            console.log(newData);
        })
        .catch(error => {
            // ...handle/report error...
        })
        .finally(() => {
            --queueLength;
            console.log("Completed work item, queue length: " + queueLength);
        });
}

Live Example:

const rnd = (max) => Math.floor(Math.random() * max);

// Fake a sequence of socket.io events
for (let i = 0; i < 5; ++i) {
    setTimeout(() => {
        getAPIData({symbol: i});
    }, 100 * (i + 1));
}

// Fake axios
const axios = {
    get(url) {
        console.log(`Start ${url}`);
        const [, num] = /(\d+)$/.exec(url);
        return new Promise(resolve => setTimeout(() => {
            console.log(`End ${url}`);
            resolve(num);
        }, rnd(800)));
    },
};

let queueLength = 0;
let sequencer = Promise.resolve();
function getAPIData(data){
    const {symbol} = data;
    ++queueLength;
    console.log("Added work item, queue length: " + queueLength);
    sequencer = sequencer
        .then(() => axios.get(`https://api.nasa.gov/planetary/apod?name=${symbol}`))
        .then((newData) => {
            console.log(newData);
        })
        .catch(error => {
            // ...handle/report error...
        })
        .finally(() => {
            --queueLength;
            console.log("Completed work item, queue length: " + queueLength);
        });
}
.as-console-wrapper {
    max-height: 100% !important;
}

Here's how that works:

  1. We start with a fulfilled promise in sequencer.
  2. Each time getAPIData is called, it waits for the previous promise to be settled before doing the axios call
  3. We assign the promise from the promise chain as the new value of sequencer so the next pass waits on it.

That does mean that each call to axios won't be done until the previous one finishes; there will never be any overlap. But if you just want the results in order, you can overlap the calls to axios, like this:

let queueLength = 0;
let sequencer = Promise.resolve();
function getAPIData(data){
    const {symbol} = data;
    ++queueLength;
    console.log("Added work item, queue length: " + queueLength);
    sequencer = Promise.all([
            sequencer,
            axios.get(`https://api.nasa.gov/planetary/apod?name=${symbol}`)
        ])
        .then(([, newData]) => { // Note the destructuring on this line
            console.log(newData);
        })
        .catch(error => {
            // ...handle/report error...
        })
        .finally(() => {
            --queueLength;
            console.log("Finished work item, queue length: " + queueLength);
        });
}

That way, the axios calls can overlap, but we still do the logging in order.

Live Example:

const rnd = (max) => Math.floor(Math.random() * max);

// Fake a sequence of socket.io events
for (let i = 0; i < 5; ++i) {
    setTimeout(() => {
        getAPIData({symbol: i});
    }, 100 * (i + 1));
}

// Fake axios
const axios = {
    get(url) {
        console.log(`Start ${url}`);
        const [, num] = /(\d+)$/.exec(url);
        return new Promise(resolve => setTimeout(() => {
            console.log(`End ${url}`);
            resolve(num);
        }, rnd(800)));
    },
};

let queueLength = 0;
let sequencer = Promise.resolve();
function getAPIData(data){
    const {symbol} = data;
    ++queueLength;
    console.log("Added work item, queue length: " + queueLength);
    sequencer = Promise.all([
            sequencer,
            axios.get(`https://api.nasa.gov/planetary/apod?name=${symbol}`)
        ])
        .then(([, newData]) => { // Note the destructuring on this line
            console.log(newData);
        })
        .catch(error => {
            // ...handle/report error...
        })
        .finally(() => {
            --queueLength;
            console.log("Finished work item, queue length: " + queueLength);
        });
}
.as-console-wrapper {
    max-height: 100% !important;
}

Example output of that:

Added work item, queue length: 1
Start https://api.nasa.gov/planetary/apod?name=0
Added work item, queue length: 2
Start https://api.nasa.gov/planetary/apod?name=1
Added work item, queue length: 3
Start https://api.nasa.gov/planetary/apod?name=2
End https://api.nasa.gov/planetary/apod?name=2
Added work item, queue length: 4
Start https://api.nasa.gov/planetary/apod?name=3
End https://api.nasa.gov/planetary/apod?name=0
0
Finished work item, queue length: 3
Added work item, queue length: 4
Start https://api.nasa.gov/planetary/apod?name=4
End https://api.nasa.gov/planetary/apod?name=3
End https://api.nasa.gov/planetary/apod?name=1
1
Finished work item, queue length: 3
2
Finished work item, queue length: 2
3
Finished work item, queue length: 1
End https://api.nasa.gov/planetary/apod?name=4
4
Finished work item, queue length: 0

Notice that even though the call for name=2 finished before the call for name=0, 0 was logged before 2.

T.J. Crowder
  • 1,031,962
  • 187
  • 1,923
  • 1,875
  • @MaygaFatmawati - Explained it differently how? (I figured the code was just an example. But the solution above applies generally, not just to this example.) – T.J. Crowder Mar 07 '22 at 10:47
  • Thank you for the answer, I made a mistake of using a wrong example. I have added my actual code now. – Mayga Fatmawati Mar 07 '22 at 10:53
  • @MaygaFatmawati - I don't see any fundamental difference, in both cases you're doing a call to an `async` function. The above seems like it applies just fine. But separately: It's not okay on Stack Overflow to edit the question so that it makes existing answers look like they're not related to the question. We should roll back the edit. If you can't apply the above to your real code, that might be a different question, but I think you probably can apply it. Give it a try, and if you run into a **specific** problem doing so, post a question about that new problem. – T.J. Crowder Mar 07 '22 at 10:57
  • Ok, I am testing your example on my code now, is there a way to see the que of waiting functions. if this works the way I need, I need to know if we can handle the amount of functions executing, because its "never" ending – Mayga Fatmawati Mar 07 '22 at 11:18
  • @MaygaFatmawati - You can track work coming into the queue (each all to `getAPIData`) and coming out (each promise completion), I've updated the answer to show keeping track of the queue length, but it could also be an array (adding: `queue.push(/*...*/)`, removing: `queue.unshift();`). – T.J. Crowder Mar 07 '22 at 11:26
  • 1
    This does indeed what I need. thank you very much for the help. One thing I don't understand is the .then([, newData]) destructuring. – Mayga Fatmawati Mar 07 '22 at 11:52
  • @MaygaFatmawati - `Promise.all` fulfills its promise with an array of the fulfillment values from the input promises. In our case, the first of those is just the sequencer, and the second is the one we want (from "axios" in the example). `[, newData]` takes the fulfillment value from the second one and puts it in `newData`. – T.J. Crowder Mar 07 '22 at 12:06