2

I'm passing an async function to an array reduce function. What's the syntax for catching errors thrown by the passed in function? The reducing is happening inside a try catch block, which is catching other errors just fine, but node gives me an UnhandledPromiseRejectionWarning if the passed in function itself throws an error.

Code:

aFunction = async (anArray) => {
  try {
    const result = await anArray.reduce(async (a, b) => {
      await doSomethingTo(b);
    }, Promise.resolve());

    return result;
  }

  catch (error) {
    winston.error(error);
  }  
}

(Edit) Actual code:

exports.chainedQueryDB = async (queries, finalTask, download) => {
  let client = await pool.connect();
  try {
    winston.info(`Begin chained table query.`);
    // Loop through query array
    const result = await queries.reduce(async (a, b) => {
      await client.query(b);
    }, Promise.resolve());

    if (download) {
      return streamOut(download, client);
    }

    return result.rows;
  }

  catch (error) {
    throw error;
  }

  finally {
    const final = await client.query(finalTask);
    winston.info(`Temp table dropped.`);
    client.release();
  }
}

(Edit) Report: Replacing await client.query(b) with await a; return client.query(b); solved the problem. With just await client.query(b), reduce seemed to 1) generate a bunch of floating client.query calls that all ran even if an earlier promise was rejected, and 2) caused an unhandled promise rejection warning. Using await a; return client.query(b); stops execution on first rejection and the catch block catches the error as originally intended.

sheepgobeep
  • 743
  • 11
  • 25
  • What do you expect the `result` to be? Why are you using `reduce`? Do you want the `doSomething` tasks to happen concurrently? – Bergi Aug 18 '17 at 02:49
  • `anArray` is a set of Postgres queries that need to happen in sequence. `result` is the result of the final pg query operation, and is returned to the calling function. The code works as written, but simulating a database error causes node to throw `UnhandledPromiseRejectionWarning`. – sheepgobeep Aug 18 '17 at 02:57
  • Actually, the code in your question does *not* run the queries in sequence, and `result` is always `undefined`. – Bergi Aug 18 '17 at 03:07
  • Shoot. Accidentally working code is the worst. The code definitely executes the items in `anArray` in sequence and `result` definitely holds the value I expect. What am I missing? – sheepgobeep Aug 18 '17 at 03:16
  • Have you shown us your actual code? What is `doSomething` - maybe it does some internal queuing? – Bergi Aug 18 '17 at 03:21
  • Hm, even in the actual code I can't see how `result` could be anything else than `undefined`. The return value of the `reduce` is always a promise that fulfills with nothing. – Bergi Aug 18 '17 at 03:30
  • What is purpose of `try..catch` at code at Question? – guest271314 Aug 18 '17 at 03:41
  • _"but node gives me an `UnhandledPromiseRejectionWarning` if the passed in function itself throws an error"_ Are you sure that an error is thrown? Or is a rejected `Promise` not handled? – guest271314 Aug 18 '17 at 03:47

2 Answers2

3

You need to do something with the promise in the accumulator (the a parameter) as well - await it, handle its errors by installing a .catch() callback, wait for it concurrently with the doSomething(b). For a sequential execution, you could do

async function aFunction(anArray) {
  try {
    return await anArray.reduce(async (a, b) => {
      await a; // make sure the previous query is done
      return doSomethingTo(b);
    }, Promise.resolve());
  } catch (error) {
    winston.error(error);
  }
}

I would hover recommend to simply not use reduce here:

async function aFunction(anArray) {
  try {
    let result;
    for (const b of anArray) {
      result = await doSomethingTo(b);
    }
    return result;
  } catch (error) {
    winston.error(error);
  }
}
Bergi
  • 630,263
  • 148
  • 957
  • 1,375
  • How does Answer address issue described at Question? – guest271314 Aug 18 '17 at 02:51
  • @guest271314 It shows how to avoid the unhandled rejection warning. – Bergi Aug 18 '17 at 02:53
  • What is the difference between the code at Question and code at Answer? – guest271314 Aug 18 '17 at 02:55
  • @guest271314 What difference don't you see? It should be obvious that they're two different things. – Bergi Aug 18 '17 at 03:12
  • the main difference is this added code - `await a; // make sure the previous query is done return` - it's quite obvious :p – Jaromanda X Aug 18 '17 at 03:16
  • @Bergi You have updated your Answer. No, did not see an obvious difference between your original code and code at OP. Perhaps you can describe the specific differences? Though the solution is still to chain `.catch()` or use second parameter to `.then()` to avoid `UnhandledPromiseRejectionWarning`, yes? – guest271314 Aug 18 '17 at 03:19
  • Does `await doSomethingTo(b)` not cause the function to wait for promise fulfillment before executing the next `await doSomethingTo(b)` of the `reduce` loop? – sheepgobeep Aug 18 '17 at 03:20
  • @sheepgobeep `await doSomethingTo(b)` does await `Promise` fulfillment before proceeding to next iterable within `for..of` loop. Though that is not the Question. – guest271314 Aug 18 '17 at 03:22
  • @guest271314 The second snippet (which was my original answer before receiving clarification from the OP) does use a `for of` loop instead of `reduce`, which makes the whole loop suspend on `await`ing the `doSomething` call. – Bergi Aug 18 '17 at 03:23
  • @sheepgobeep Yes, it causes the function to wait before executing the next one, which is exactly what we want. – Bergi Aug 18 '17 at 03:24
  • @guest271314 Using `.catch()` is useful only for [fire-and-forget asynchronous calls](https://stackoverflow.com/a/32385430/1048572). We don't want that here. – Bergi Aug 18 '17 at 03:25
  • @Bergi The second snippet is not your original answer. How does your original post address `UnhandledPromiseRejectionWarning`? – guest271314 Aug 18 '17 at 03:25
  • @guest271314 By not using `reduce` (or, by not using it without an appropriate callback that handles `a`), there are no promises created that are never awaited, and therefore won't be unhandled when they reject. – Bergi Aug 18 '17 at 03:27
  • @Bergi Not following your logic as to the `UnhandledPromiseRejectionWarning` message and not using `.reduce()`. From perspective here `.reduce()` is not the issue. OP could debug own code to find exactly where the issue is by using `window.onunhandledrejection`. It is not clear why `try..catch` is being used within code. Though perhaps you will be able to help OP with overall code, where here, unable to get past simply utilizing `.catch()` – guest271314 Aug 18 '17 at 03:32
  • So `await a; return doSomethingTo(b);` is something totally different than doing `await doSomethingTo(b);`? – sheepgobeep Aug 18 '17 at 03:32
  • 2
    @sheepgobeep Yes. (It could've been `await doSomething(b)` instead of the `return`, that doesn't make a difference). But by awaiting `a`, we don't leave the `a` promise - the result of the previous iteration - hanging around nowhere. Maybe it helps to write out what `reduce` does with its callback and accumulator for a small example (say, `anArray` of three elements) on a sheet of paper. – Bergi Aug 18 '17 at 03:41
0

To avoid the UnhandledPromiseRejectionWarning you can chain .catch() to aFunction() call or utilizing second parameter of .then() to handle the rejected Promise or error.

Alternatively, chain .catch() to doSomethingTo(b) call to handle the error.

guest271314
  • 1
  • 15
  • 104
  • 177