Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Cleaner way to catch an error in a chain of nested promises?

I'm new to working with promises and am noticing that, in order to catch an error in a chain of nested promises, I need to invoke the catch method on each promise in the chain. Is there a cleaner way to write this?

      poll.pollForCandidates().then((candidates) => {
        let clientId = candidates[0].clientId;
        poll.getUnprocessedCandidates({context, clientId, candidates})
          .then((unprocessedCandidates) => {
            console.log(unprocessedCandidates);
            poll.addCandidatesToQueue(context, unprocessedCandidates)
              .then((processedCandidates) => {
                console.log(processedCandidates);
                poll.addCandidatesToTable(processedCandidates)
                  .then((result) => {
                    console.log(result);
                  })
                  .catch((error) => {
                    console.log(error);
                  });
              })
              .catch((error) => {
                console.log(error); 
              })
          })
          .catch((error) => {
             console.log(error); 
          })
      })
      .catch(() => {
        console.done(error); 
      });
    };
like image 963
Michael P. Avatar asked Jul 10 '17 03:07

Michael P.


2 Answers

This is the shorter way to do it:

poll.pollForCandidates()
    .then((candidates) => poll.getUnprocessedCandidates({ context, candidates[0].clientId, candidates }))
    .then((unprocessedCandidates) => poll.addCandidatesToQueue(context, unprocessedCandidates))
    .then((processedCandidates) => poll.addCandidatesToTable(processedCandidates))
    .then(console.log)
    .catch(console.error);

Explanation:

This is called composing promises, and it's one of the great superpowers of promises. Each function will only be called when the previous promise has resolved, and it'll be called with that promise's output.

There are three things you can do inside the then function:

  1. return another promise
  2. return a synchronous value (or undefined)
  3. throw a synchronous error

Here in the example above, we return a promise, so we can use .then to continue the composing promises.

At the end, the console.log gets the result and call the console.log with it. (Same with the console.error)

That's it. Once you understand this trick, you understand promises.

You can continue reading about it here: We have a problem with promises - By: Nolan Lawson

like image 62
sidanmor Avatar answered Oct 12 '22 23:10

sidanmor


Like @bugwheels94 mentioned, you can return your promises from within .then and add another .then outside of the previous .then. Also, .then can take 2 arguments - the second one is for catching errors. It's a bit difficult to explain in words so here's an example.

poll.pollForCandidates()
  .then((candidates) => {
    let clientId = candidates[0].clientId;

    return poll.getUnprocessedCandidates({context, clientId, candidates})
      .then((unprocessedCandidates) => {
        console.log(unprocessedCandidates);
        return poll.addCandidatesToQueue(context, unprocessedCandidates)
      }, (error) => {
         console.log(error); 
      })
      .then((processedCandidates) => {
        console.log(processedCandidates);
        return poll.addCandidatesToTable(processedCandidates)
      }, (error) => {
        console.log(error); 
      })
      .then((result) => {
        console.log(result);
      }), (error) => {
        console.log(error);
      });
  })
  .catch((error) => {
    console.log(error);
  });

Edit: @jfriend00 brought up a good about using all of those .catch methods. Here's what the code would look like with a single .catch at the end of the chain.

poll.pollForCandidates()
  .then((candidates) => {
    let clientId = candidates[0].clientId;

    return poll.getUnprocessedCandidates({context, clientId, candidates})
      .then((unprocessedCandidates) => {
        console.log(unprocessedCandidates);
        return poll.addCandidatesToQueue(context, unprocessedCandidates)
      })
      .then((processedCandidates) => {
        console.log(processedCandidates);
        return poll.addCandidatesToTable(processedCandidates)
      })
      .then((result) => {
        console.log(result);
      });
  })
  .catch((error) => {
    console.log(error);
  });

Edit 2: Removing those extra console.log statements allows you to make it even cleaner.

poll.pollForCandidates()
  .then((candidates) => {
    let clientId = candidates[0].clientId;

    return poll.getUnprocessedCandidates({context, clientId, candidates})
      .then(unprocessedCandidates => poll.addCandidatesToQueue(context, unprocessedCandidates))
      .then(poll.addCandidatesToTable)
      .then((result) => {
        console.log(result);
      });
  })
  .catch((error) => {
    console.log(error);
  });
like image 39
DonovanM Avatar answered Oct 13 '22 00:10

DonovanM