Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: allow UnhandledPromiseRejection errors in BulkWriter if no error handler is specified #1572

Merged
merged 3 commits into from
Jul 30, 2021

Conversation

thebrianchen
Copy link

BulkWriter should throw an error if an operation promise is rejected without a custom error handler. Otherwise, failures are silently swallowed without any indication to the end-developer. If an error handler is specified with onWriteError(), BulkWriter assumes that the developer is handling the error and will swallow failed promises so that the developer does not have to add a dangling catch() statement for every promise.

@thebrianchen thebrianchen requested review from a team as code owners July 28, 2021 21:38
@product-auto-label product-auto-label bot added the api: firestore Issues related to the googleapis/nodejs-firestore API. label Jul 28, 2021
@google-cla google-cla bot added the cla: yes This human has signed the Contributor License Agreement. label Jul 28, 2021
* @internal
*/
private _silencePromiseIfErrorHandlerSet(promise: Promise<unknown>): void {
if (this._errorHandlerSet) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we do this slightly later and make this decision when the operation returns? This would allow the following:

bulkWriter.set();
bulkWriter.set();
bulkWriter.onError(...);

Note that this error handler will always be attached before any result is returned.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's the use case for changing the error handler after an operation has been enqueued but before it resolves? To me, it seems like an anti-pattern, or an accidental bug.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's guaranteed that this code executes before the request is processed, so I don't think we should forbid this. It is not possible for any network task to resolve before all synchronous code finished executing. I do think it is cleaner to attach the error handler first, but we do not enforce this, so I think we should allow this even if we believe this to be an anti-pattern.

Copy link
Contributor

@schmidt-sebastian schmidt-sebastian left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you. Some final words but this is basically good to go.

//
// This is done here in order to chain the caught promise onto `lastOp`,
// which ensures that flush() resolves after the operation promise.
const returnedBulkWriterPromise = bulkWriterOp.promise.catch(err => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should probably use "op" instead of BulkWriter since it only affects a single operation.

I would have also used the term "user" in this variable, but that may be the lack of oxygen at my current elevation (userPromise).

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

renamed to userPromise

@@ -54,6 +54,7 @@ import {
verifyInstance,
} from './util/helpers';
import api = proto.google.firestore.v1;
import {Deferred} from '../src/util';
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you move this up to the other src imports?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done.

* @private
* @internal
*/
private _errorHandlerSet = false;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think you need this. You can just do just do:

const DEFAULT_ERROR_FUNCTION = error => {
    const isRetryableDeleteError =
      error.operationType === 'delete' &&
      (error.code as number) === StatusCode.INTERNAL;
    const retryCodes = getRetryCodes('batchWrite');
    return (
      (retryCodes.includes(error.code) || isRetryableDeleteError) &&
      error.failedAttempts < MAX_RETRY_ATTEMPTS
    );
  };

private _errorFn = DEFAULT_ERROR_FUNCTION;

if (this._errorFn !== DEFAULT_ERROR_FUNCTION) {...}

Up to you.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I remember wanting to do something similar for the type converters, and we had to use .toString(). I'd prefer keeping it simpler and (imo) more readable with a boolean variable.

@thebrianchen thebrianchen merged commit e862ac8 into master Jul 30, 2021
@thebrianchen thebrianchen deleted the bc/bulk-unhandled branch July 30, 2021 19:37
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
api: firestore Issues related to the googleapis/nodejs-firestore API. cla: yes This human has signed the Contributor License Agreement.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

2 participants