11

Recently AWS announced the availability of the nodejs8.10 runtime for their lambda functions (Node.js 8.10 runtime available). While this seemed great for the happy flow, I'm running into some problems with the unhappy flow, i.e. I'm getting 'UnhandledPromiseRejectionWarnings'.

I'm using the below code. The idea is that I provide a key to a non-existent object to test out the unhappy flow for that scenario. My goal is for the error to get logged and propagated out of the lambda since I have no sane way of handling it here (I have no way to retrieve a new key within this lambda function). I also would like to be able to use the error in the caller (e.g. another lambda function or step functions).

'use strict';

const AWS = require('aws-sdk');

exports.handler = async (event) => {
  let data;
  try {
    data = await getObject(event.key);
  } catch (err) {
    console.error('So this happened:', err);
    throw err;
  }

  return data;
}

const getObject = async (key) => {
  let params = {
    Bucket: process.env.BUCKET,
    Key: key
  };

  const s3 = new AWS.S3();

  let data;
  try {
    data = await s3.getObject(params).promise();
  } catch(err) {
    console.log('Error retrieving object');
    throw err;
  }

  console.log('Retrieved data');
  return data.Body.toString('utf8');
}

If I run this lambda function (with SAM local) I get the error back out of the lambda like I want, but I also get the following warning(s):

2018-04-18T07:54:16.217Z    6ecc84eb-46f6-1b08-23fb-46de7c5ba6eb    (node:1) UnhandledPromiseRejectionWarning: NoSuchKey: The specified key does not exist.
    at Request.extractError (/var/task/node_modules/aws-sdk/lib/services/s3.js:577:35)
    at Request.callListeners (/var/task/node_modules/aws-sdk/lib/sequential_executor.js:105:20)
    at Request.emit (/var/task/node_modules/aws-sdk/lib/sequential_executor.js:77:10)
    at Request.emit (/var/task/node_modules/aws-sdk/lib/request.js:683:14)
    at Request.transition (/var/task/node_modules/aws-sdk/lib/request.js:22:10)
    at AcceptorStateMachine.runTo (/var/task/node_modules/aws-sdk/lib/state_machine.js:14:12)
    at /var/task/node_modules/aws-sdk/lib/state_machine.js:26:10
    at Request.<anonymous> (/var/task/node_modules/aws-sdk/lib/request.js:38:9)
    at Request.<anonymous> (/var/task/node_modules/aws-sdk/lib/request.js:685:12)
    at Request.callListeners (/var/task/node_modules/aws-sdk/lib/sequential_executor.js:115:18)
2018-04-18T07:54:16.218Z    6ecc84eb-46f6-1b08-23fb-46de7c5ba6eb    (node:1) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). (rejection id: 1)
2018-04-18T07:54:16.218Z    6ecc84eb-46f6-1b08-23fb-46de7c5ba6eb    (node:1) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.

I'm not sure how to handle this while still propagating the error out of the lambda function (which should be a valid scenario according to lambda function errors (node.js))

I've also tried running a similar (to me) scenario like the one below (to try and pinpoint and understand the error) but somehow here I do not get the warning and it is working as intended (the error is returned from the lambda function).

'use strict';

const AWS = require('aws-sdk');

exports.handler = async (event) => {
  let data;
  try {
    data = await directBoom();
  } catch (err) {
    console.error('So this happened:', err);
    throw err;
  }

  return data;
}

const directBoom = async () => {
  let data;
  try {
    data = await Promise.reject(new Error('boom!')); 
  } catch(err) {
    throw err;
  }

  return data;
}

What am I missing here and why are the two examples behaving differently? How do I get rid of the warning in the first example while still being able to propagate the error out of the lambda function? Any help would be appreciated.

CodeVision
  • 111
  • 1
  • 1
  • 4
  • I'm having the same problem. The AWS blog on the Node 8.10 runtime just returns Promises, see https://amzn.to/2J1Y49r but they don't discuss how rejections are handled. Really strange. Maybe ask on the official AWS forums? – squidfunk Jun 01 '18 at 19:32
  • Just posted a question on the official AWS forum regarding this topic: https://amzn.to/2JpoHEY – squidfunk Jun 03 '18 at 16:14
  • my minimal test worked as expected `exports.handler = async (event) => { throw Error('rejected') };`. The lambda fails with my rejection error. Could this be a sam error – everett1992 Jun 08 '18 at 05:06
  • Hmm, your minimal test still results in unhandled promise rejections. – squidfunk Jun 08 '18 at 13:09
  • @MartinDonath Are you sure you are using the nodejs 8.10 runtime? Is the bug in SAM local? – everett1992 Jun 08 '18 at 16:35
  • Yes, definitely using the Node 8.10 runtime as the 6.10 runtime would throw a syntax error due to the `async` keyword in your test case. SAM - never used it, I just went to the Lambda console, created a new function from your testcase and that was the result. – squidfunk Jun 12 '18 at 09:28
  • Just found another example where they just throw in an async function in the *official* AWS docs: https://docs.aws.amazon.com/lambda/latest/dg/nodejs-prog-mode-exceptions.html – squidfunk Jun 12 '18 at 09:31

3 Answers3

8

Any time you throw or reject within a promise/async function and don't handle it with a catch, Node is going to return that warning.

The example on AWS doesn't throw the error in the catch block, it returns it:

let AWS = require('aws-sdk');
let lambda = new AWS.Lambda();
let data;

exports.handler = async (event) => {
    try {
        data = await lambda.getAccountSettings().promise();
    }
    catch (err) {
        console.log(err);
        return err;
    }
    return data;
};

In a large application with lots of asynchronous functions, having a single unhandled promise terminate the node process would be bad. That may not apply to a simple Lambda function where throwing an error that terminates the process is desired behavior. But if you don't want Node to warn you, return the error instead.

  • 2
    Although indeed in that example they do return the error, that kind of seems a non-solution. By just returning the error, you're hiding the fact that its an error and will have to resort to your own error handling solution to distinguish between regular return values and error return values instead of language mechanics. Either way, in the [link](https://docs.aws.amazon.com/lambda/latest/dg/nodejs-prog-mode-exceptions.html) I posted in the original post as well as my second example, an error is thrown without it leading to the warning. So it seems somehow possible, I'm just not sure how... – CodeVision Jun 20 '18 at 07:37
  • The documentation clearly says that: **1. When this Lambda function is invoked, it will notify AWS Lambda that function execution completed with an error and passes the error information to AWS Lambda. You would get the same result if you write the function using the async feature of Node.js runtime version 8.10.** and **2. Custom error handling makes it easier to create serverless applications. This feature integrates with all the languages supported by the Lambda Programming Model [...]**. I think that they must update the AWS Lambda to support the nodejs async/await error handling feature. – Rosberg Linhares Jun 21 '18 at 21:56
3

It seems that AWS has fixed the issue. Testing with the following function using the Node 8.10 runtime, I'm not seeing any unhandled rejections anymore:

exports.handler = async (event) => { 
  throw new Error("broken")
};
squidfunk
  • 391
  • 3
  • 17
1

I managed that by using the fail method from the context object that notifies Lambda function that the execution failed.

'use strict'

exports.handler = async function (event, context) {
  try {
    throw new Error('Something went wrong')
  } catch (err) {
    context.fail(err)
  }
}
  • I'd personally have expected them to handle that into a `catch` statement they would have attached to an exported async function. – Kevin Rambaud Jul 09 '18 at 21:58