4

Using Express, I am using .map to create an array of API calls, whose results I want to combine into one response object. Since every call uses different query parameters, I would like to use the query parameters as the response object's keys.

I create the GET requests using axios, which returns a promise, and then I use axios.all to wait for all of the promises to resolve.

The problem is that after the promises resolve, I no longer have access to the variables used to create them. How can I attach those variables to the promises for later reference?

Here is the API:

router.get('/api', (req, res) => {
  const number = req.query.number;
  res.json({ content: "Result for " + number });
});

Here is where I am trying to combine the results:

router.get('/array', async (req, res) => {
  res.locals.payload = {};
  const arr = [1, 2, 3, 4, 5];
  const promises = arr.map(number => {
    return axiosInstance.get('/api', { params: { number: number } })
  });
  const results = await axios.all(promises);
  results.map(r => {
    // number should match original, but I no longer have
    // access to the original variable
    number = 1;
    res.locals.payload[number] = r.data;
  });
  res.json(res.locals.payload);
});

Result of GET on /array:

{
    "1": {
        "content": "Result for 5"
    }
}

What can I do when creating the Promise objects to preserve the keys?

wbruntra
  • 1,021
  • 1
  • 10
  • 18
  • The index number in the array [**does** match the original](https://stackoverflow.com/q/28066429/1048572)! – Bergi Aug 17 '17 at 15:23

1 Answers1

0

If the result were going to be an array with 0-based indexes instead of an object with properties "1", "2", etc., we could solve it with Promise.all (or axios.all if it offers the same guarantee Promise.all does that the array it gives you will be in the order of the promises you give it, regardless of the order in which they resolve). But I'm guessing your "number" is really a placeholder for something more interesting. :-)

You can do this by making use of the fact that a promise is a pipeline where the various handlers transform the contents along the way.

In this case, you can transform the result of the get call into an object with the number and result:

const promises = arr.map(number => {
  return axiosInstance.get('/api', { params: { number } })
         .then(result => ({result, number}));
});

Now, results in your all.then callback receives an array of objects with result and number properties. You could use destructuring parameters to receive them in your results.map callback if you like:

//           vvvvvvvvvvvvvvvv---------------- destructuring parameters
results.map(({result, number}) => {
  res.locals.payload[number] = result.data;
//                   ^^^^^^----^^^^^^-------- using them
});

Live Example:

// Fake axiosInstance
const axiosInstance = {
    get(url, options) {
        return new Promise(resolve => {
            setTimeout(() => {
                resolve({data: `Data for #${options.params.number}`});
            }, 300 + Math.floor(Math.random() * 500));
        });
    }
};
// Fake axios.all:
const axios = {
    all: Promise.all.bind(Promise)
};

// Fake router.get callback:
async function routerGet() {
    const payload = {}; // stand-in for res.locals.payload
    const arr = [1, 2, 3, 4, 5];
    const promises = arr.map(
      number => axiosInstance.get('/api', { params: { number } })
                             .then(result => ({result, number}))
    );
    const results = await axios.all(promises);
    results.map(({result, number}) => {
      /*res.locals.*/payload[number] = result.data;
    });
    console.log(/*res.locals.*/payload);
}

// Test it
routerGet().catch(e => { console.error(e); });

Note I've used ES2015+ property shorthand above, since you're using arrow functions and such. The object initializer { number } is exactly the same as { number: number }.


Side note 2: You can use a concise arrow function for the map callback if you like:

const promises = arr.map(
  number => axiosInstance.get('/api', { params: { number } })
                         .then(result => ({result, number}))
);
T.J. Crowder
  • 1,031,962
  • 187
  • 1,923
  • 1,875