Home > OS >  Node.js concurrent API GETs, return as soon as one responses
Node.js concurrent API GETs, return as soon as one responses

Time:07-02

I have a list of APIs I want to call GET simultaneously on all of them and return as soon as one API finishes the request with a response code of 200.

I tried using a for-loop and break, but that doesn't seem to work. It would always use the first API

import axios from 'axios';

const listOfApi = ['https://example.com/api/instanceOne', 'https://example.com/api/instanceTwo'];

for (const api of listOfApi) {
    try {
        response = await axios.get(api, {
            data: {
                url: 'https://example.com/',
            },
        });
        break;
    } catch (error) {
        console.error(`Error occurred: ${error.message}`);
    }
}

CodePudding user response:

You can use Promise.race() to see which of an array of promises finishes first while running all the requests in parallel in flight at the same time:

import axios from 'axios';

const listOfApi = ['https://example.com/api/instanceOne', 'https://example.com/api/instanceTwo'];

Promise.any(listOfApi.map(api => {
    return axios.get(api, {data: {url: 'https://example.com/'}}).then(response => {
        // skip any responses without a status of 200
        if (response.status !== 200) {
            throw new Error(`Response status ${response.status}`, {cause: response});
        }
        return response;
    });
})).then(result => {
    // first result available here
    console.log(result);
}).catch(err => {
    console.log(err);
});

Note, this uses Promise.any() which finds the first promise that resolves successfully (skipping promises that reject). You can also use Promise.race() if you want the first promise that resolves or rejects.

CodePudding user response:

I think jfriend00's answer is good, but I want to expand on it a bit and show how it would look with async/await, because that's what you are already using.

As mentioned, you can use Promise.any (or Promise.race). Both take an array of promises as argument. Promise.any will yield the result of the first promise that resolves successfully, while Promise.race will simply wait for the first promise that finishes (regardless of whether it was fulfilled or rejected) and yield its result.

To keep your code in the style of async/await as it originally was, you can map the array using an async callback function, which will effectively return a promise. This way, you don't have to "branch off into .then territory" and can keep the code more readable and easier to expand with conditions, etc.

This way, the code can look as follows:

import axios from 'axios';

const listOfApi = ['https://example.com/api/instanceOne', 'https://example.com/api/instanceTwo'];

try {
    const firstResponse = await Promise.any(listOfApi.map(async api => {
        const response = await axios.get(api, {
            data: {
                url: 'https://example.com/',
            },
        });
        
        if (response.status !== 200) {
            throw new Error(`Response status ${response.status}`, {cause: response});
        }
        
        return response;
    }));
    
    // DO SOMETHING WITH firstResponse HERE
} catch (error) {
    console.error('Error occured:', error);
}

Side note: I changed your console.error slightly. Logging only error.message is a common mistake that hinders you from effective debugging later on, because it will lack a lot of important information because it prints only the message and not the error stack, the error name or any additional properties the error may have. Simply coercing the error to a string without .message will already be better as it includes name and stack then, but what's best is to supply the error as separate argument to console.error so that inspect gets called on it and it can print the whole error object, with stack and any additional properties you may be interested in. This is very valuable when you encounter an error in production that is not so easy to reproduce.

  • Related