aws-sdk-js: AWSError not backed by actual prototype

It seems that the AWSError object that is exported here https://github.com/aws/aws-sdk-js/blob/master/lib/error.d.ts is not actually backed by an actual class. This means that, at runtime, instanceof checks can have a nondeterministic execution. With transpilation and Chrome, the runtime effect is that the prototype chain of the aws-sdk module object created by webpack transpilation is checked. In Safari, it just throws with one of these instanceof errors: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Errors/invalid_right_hand_side_instanceof_operand

While I love that we added some typings around AWSError (https://github.com/aws/aws-sdk-js/issues/1219), we should be careful about vending classes if they aren’t backed by actual implementations. The best fix is if the actual prototype backing AWSError can be exported, but I couldn’t find such an implementation. A cursory search for how error objects are created by the SDK surfaces https://github.com/aws/aws-sdk-js/blob/master/lib/util.js#L547, but that doesn’t look like it’s the actually creating the initial AWSError object. The other solution is to vend AWSError as an interface, but this is a potentially breaking change for consumers.

Is there a recommendation here?

About this issue

  • Original URL
  • State: open
  • Created 5 years ago
  • Reactions: 40
  • Comments: 21 (4 by maintainers)

Most upvoted comments

We are just now running into a problem with this with typescript. When we get an AWSError back from dynamoDB we would like to inspect it to decide what actions to take. Without the ability to use instanceOf, we are unable to accomplish this in a type-safe way.

I’m not concerned about exceptions that can’t be typed, but the ones that are already typed by the SDK as AWSError. If they are exposed as AWSError and AWSError is a class, then it is idiomatic to use instanceof. Checking for a key’s existence as a means of type checking doesn’t provide the same guarantee that instanceof can provide. Any custom error can implement the same key. If I want to have some control flow with regard to my error handling, this will break. If I want to handle an AWSError differently from another error, I’d like some help from the SDK to do so. TS may not type the exceptions but I can surely check the exception’s prototypes and handle accordingly. Ideally AWSError would be a class that I can use instanceof on, but if that is not possible it should be vended as an interface so that it is clear that such behavior cannot be depended on.

See the following for custom errors and use with typescript:

Maybe my comment isn’t related to what the author states but if you try to use the actual AWSError from the core library as constructor, something like:

import { AWSError } from 'aws-sdk';

let err: AWSError = new AWSError('Something unexpected happened on the server, please try again later');

You will get aws_sdk_1.AWSError is not a constructor on runtime as the actual AWSError class is extended by Error class but doesn’t have any constructor as ErrorConstructor class does.

Using aws-sdk v2.602.0 on my package.json file.

Had to make something like code below to get rid of error message:

let err: AWSError = new Error('Something unexpected happened on the server, please try again later') as AWSError;

Can I know what’s the usecase for this?

My use case, similar to @KingDarBoja’s perhaps, was producing my own AWSError instance in a test, in order to unit test how my code responds to an error response from an AWS service.

I was surprised by the lack of a concrete implementation. I think I’d be a lot less surprised if it were an interface.

The solution I mentioned inside the aws-sdk codebase is probably the best we can do until a major version change where the type can be made into an actual class:

This could probably be easily done by adding a unique symbol key to the errors.

To elaborate, I was thinking something like this:

  1. Add a (private) symbol key to every AWSError instance.
  2. Make an isAWSError function which checks if an object has a key with that private symbol.

Could also make use of Object.defineProperty to make the symbol non-enumerable (which I think is the default anyway for symbols) so that it doesn’t affect any code which is enumerating the properties of the errors. The result is that code can now very surely verify that something is an AWS error without affecting other code that was using it before.

Perhaps a non-breaking way to at least expose the ability to filter out AWSError is to add a function isAWSError which can act as a type guard. It would be useful outside of TypeScript too.

This could probably be easily done by adding a unique symbol key to the errors.

Also ran into this. the fact that this is labelled as class means that TypeScript will falsely assume that it’s an actual JavaScript object with a prototype, while in fact there isn’t anything exported in the module named AWSError. It should be changed to an interface to reflect that.

This was the exact same issue I had. We ended up just checking if the error has a code property that matched some of the AWS error codes but it’s not ideal.

While we’re talking about errors can we also talk about how all of the stack traces are broken? They always only show an internal stack trace of the sdk and lose all of the original stack trace of the calling aplications code.

For example I just saw this:

InvalidParameterValue: Value 45813 for parameter VisibilityTimeout is invalid. Reason: VisibilityTimeout must be an integer between 0 and 43200.
    at Request.extractError (/app/node_modules/aws-sdk/lib/protocol/query.js:47:29)
    at Request.callListeners (/app/node_modules/aws-sdk/lib/sequential_executor.js:105:20)
    at Request.emit (/app/node_modules/aws-sdk/lib/sequential_executor.js:77:10)
    at Request.emit (/app/node_modules/aws-sdk/lib/request.js:683:14)
    at Request.transition (/app/node_modules/aws-sdk/lib/request.js:22:10)
    at AcceptorStateMachine.runTo (/app/node_modules/aws-sdk/lib/state_machine.js:14:12)
    at /app/node_modules/aws-sdk/lib/state_machine.js:26:10
    at Request.<anonymous> (/app/node_modules/aws-sdk/lib/request.js:38:9)
    at Request.<anonymous> (/app/node_modules/aws-sdk/lib/request.js:685:12)
    at Request.callListeners (/app/node_modules/aws-sdk/lib/sequential_executor.js:115:18)
    at Request.emit (/app/node_modules/aws-sdk/lib/sequential_executor.js:77:10)
    at Request.emit (/app/node_modules/aws-sdk/lib/request.js:683:14)
    at Request.transition (/app/node_modules/aws-sdk/lib/request.js:22:10)
    at AcceptorStateMachine.runTo (/app/node_modules/aws-sdk/lib/state_machine.js:14:12)
    at /app/node_modules/aws-sdk/lib/state_machine.js:26:10
    at Request.<anonymous> (/app/node_modules/aws-sdk/lib/request.js:38:9)

For some reason the sdk is throwing an unhandled error internally which is only caught by the global handler and it is not being propagated back and not displaying the calling application codes original callstack so i have no idea how to handle it.

Has anyone managed to come up with specific examples as to how to filter out an AWSError in an efficient way?

The type guard would be a great solution now that catches default to unknown https://devblogs.microsoft.com/typescript/announcing-typescript-4-4/#use-unknown-catch-variables

Agreed, but the current plan is to wait until a major version bump to do that. This doesn’t need a major version bump; it’s just a bug fix.