简体   繁体   中英

Express returns an error but Promise.allSettled is fulfilled

I am using json-server to create a simple api server. I am mainly doing this to practice my understanding of async/await and asynchronous code in javascript.

Here is my json-server start code that seeds the db and api code:

module.exports = () => {
    const data = { documents: [] }    
    for (let i = 0; i < 4000; i++) {
        data.documents.push({ id: i })
    }
    console.log('done');
    return data
}

Now I can hit GET - /documents (returns all documents) GET - /documents/id (returns a document via the ID).

I want to practice Error handling with async await, so I made middleware to return an error if the id is 4:

module.exports = (req, res, next) => {       
  if(req.originalUrl.split('/')[2] === '4') {      
    return res.status(429).send({error: 'rate limit exceeded'})
  };
  next()
}

The API structure appears to work as I intended. However, when I use Promise.allSettled to process an array of promises, the promise that returns an error has the error object as a value, but the status still says "fulfilled".

const getById = async (id) => {
    try {
        const data = await axios.get(`http://localhost:3000/documents/${id}`);    
        return data;

    } catch (e) {
        return e
    } 
}   



( async () => {
    try {
        const resolvedPromises = await Promise.allSettled([
            await getById(1),
            await getById(2),
            await getById(3),        
            await getById(4)
        ]);        
        console.log(resolvedPromises);
    } catch(e) {
        // THIS RUNS IF I THROW THE ERROR FROM getById, however Promise.allSettled stops 
        // execution.
        console.log(e);
        console.log('error');       
    }
    
})()

Here is the output when I run the code above:

[
  {
    status: 'fulfilled',
    value: {
      status: 200,
      statusText: 'OK',
      headers: [Object],
      config: [Object],
      request: [ClientRequest],
      data: [Object]
    }
  },
  {
    status: 'fulfilled',
    value: {
      status: 200,
      statusText: 'OK',
      headers: [Object],
      config: [Object],
      request: [ClientRequest],
      data: [Object]
    }
  },
  {
    status: 'fulfilled',
    value: {
      status: 200,
      statusText: 'OK',
      headers: [Object],
      config: [Object],
      request: [ClientRequest],
      data: [Object]
    }
  },
  {
    status: 'fulfilled',
    value: [AxiosError: Request failed with status code 429] {
      message: 'Request failed with status code 429',
      name: 'AxiosError',
      code: 'ERR_BAD_REQUEST',
      config: [Object],
      request: [ClientRequest],
      response: [Object]
    }
  }
]

Why is the status fulfilled vs rejected when I call await getById(4) ? Is this an issue with the value I'm returning from the express server?

Promise.allSettled is not being passed an array of promises: the await operators in

const resolvedPromises = await Promise.allSettled([
            await getById(1),
            await getById(2),
            await getById(3),        
            await getById(4)
        ]);      

are executed when the parameter value to pass to allSettled is being evaluated before making the call. Since await throws if its promise operand becomes rejected, the catch clause of the surrounding try/catch is executed as noted in the question.

Leaving out the await operators before the calls to getById may solve the issue, assuming getById calls never synchronously throw before returning a promise.


Looking more closely getData is an async function and so won't throw on being called. It is also fullfilling its return promise with an error object if the axios call rejects. If you want the promise to be settled in a rejected status, you could

  • rethrow the error in the catch clause within getById ,

  • remove the try/catch block from within getById .

  • return the axios promise from getById by reducing it to

     const getById=id=> axios.get(`http://localhost:3000/documents/${id}`);

About this part here:

THIS RUNS IF I THROW THE ERROR FROM getById, however Promise.allSettled stops execution.

Because your code is equivalent to this here:

try {
  const tmp = [];
  tmp[0] = await getById(1);
  // wait for getById(1) to finish
  tmp[1] = await getById(2);
  // wait for getById(2) to finish
  tmp[2] = await getById(3);
  // wait for getById(3) to finish
  tmp[3] = await getById(4);
  // wait for getById(4) to finish
  
  // and if any one of these promises is rejected, 
  // the code goes straight to `catch` and never even reaches the following line.

  const resolvedPromises = await Promise.allSettled(tmp);
  console.log(resolvedPromises);
} catch (e) {
  // THIS RUNS IF I THROW THE ERROR FROM getById, however Promise.allSettled stops 
  // execution.
  console.log(e);
  console.log('error');
}

These await s are not only slowing your requests down, by running them in series instead of paralell, they are breaking your code.

catch (e) { return e }

Catching the error and returning a value turns that rejected promise into a fulfilled one.

Remove the try..catch and everything will work as expected (once you also implement the suggestions in traktor's answer )

const getById = (id) => axios.get(`http://localhost:3000/documents/${id}`);

If you did want to catch failures (eg for logging), ensure you keep the resulting promise rejected

// async / await
try {
  return await axios.get(url);
} catch (err) {
  console.error("request failed", err.toJSON());
  throw err; // re-throw
}

// .then() / .catch()
return axios.get(url)
  .catch((err) => {
    console.error("request failed", err.toJSON());
    return Promise.reject(err);
  });

See also

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM