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(script-compiler): add error responses #75

Merged
merged 9 commits into from
Jun 22, 2023

Conversation

otariidae
Copy link
Contributor

ref: #72

  • add TextlintWorkerCommandResponseLintError and TextlintWorkerCommandResponseFixError
  • add error handling for Textlint worker

@otariidae otariidae force-pushed the error-handling-worker branch from 16b4b51 to 4c793fa Compare June 18, 2023 11:39
Comment on lines 154 to 159
}).catch(error => {
return self.postMessage({
id: data.id,
command: "lint:error",
error
});
Copy link
Member

@azu azu Jun 18, 2023

Choose a reason for hiding this comment

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

textlint's lintText/fixText does not reject any result in normally.
It will be unexpected result.

lint:error will make misleading.
textlint return lint error as messages instead of exception.

I think lint:error is redundant.
It is just error(unexpected error).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

OK. So, all your reviews considered, you mean we need single error response type to handle unexpected errors, right?
Fixed in 1bc8669.

Comment on lines 148 to 153
}).catch(error => {
return self.postMessage({
id: data.id,
command: "error",
error
});
Copy link
Member

Choose a reason for hiding this comment

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

Is it possible to make this just a global error?
Ideally, it would be more convenient if users could get it as a default error event in WebWorker.

https://developer.mozilla.org/en-US/docs/Web/API/Worker/error_event

// user can catch the error as global error
worker.addEventListner("error", event => {
  console.error(event);
  console.error(event.error);
});

Current error handling looks like complex.

If make this just a global error, user can just listen "error" event for error handling.

// AbortSignals can be used for event listner
// https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener#add_an_abortable_listener
const controller = new AbortController();
const onMessage = (event) => {
  // do somthing
  ontroller.abort(); // unlisten
}
// error handling
const onError = (event) => {
  // do error handling
  controller.abort(); // unlisten
}
worker.addEventListner("message", onMessage, { signal: controller.signal });
worker.addEventListner("error", onError, { signal: controller.signal });

📝 Custom Error may be needed.

class TextlintError extends Error {
  constructor(message, id){
   // https://javascript.info/custom-errors
  }
}

Copy link
Contributor Author

@otariidae otariidae Jun 19, 2023

Choose a reason for hiding this comment

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

Looks great for me! But ErrorEvent.event seems to be always null.
Moreover I found addEventListener("error", ...) can handle thrown Error but cannot handle promise rejection. I am considering another idea based on your review.

Copy link
Contributor Author

@otariidae otariidae Jun 19, 2023

Choose a reason for hiding this comment

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

I found error event listener can handle promise rejection in the worker if the worker handle unhandledrejection event and use reportError.

// in the worker
self.addEventListener("unhandledrejection", error => {
    reportError(error)
})

But I haven't found how to handle id through error event listener.

Copy link
Member

@azu azu Jun 19, 2023

Choose a reason for hiding this comment

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

Probably, reportError allow to report ErrorEvent which include custom Error object.

const error = new Error('description');
error.id = "test-1";
self.reportError(new ErrorEvent('textlint error', {
    error : error,
    message : 'description',
}));
image

Technically, it could be written as follows.

class LintError extends Error { }
lintText(...).catch(error => {
  reportError(new ErrorEvent('textlint error', {
    error : new LintError("description", { id }),
    message : 'description',
}))

However, Safari/Node.js/Deno does not support reportError in WebWorker yet.
https://developer.mozilla.org/en-US/docs/Web/API/reportError

Copy link
Contributor Author

@otariidae otariidae Jun 19, 2023

Choose a reason for hiding this comment

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

Here is a wip commit f2fcc24 including debug comments to share the situation.
In this case I got a ErrorEvent with Uncaught #<EventError> message and null error, so reportError with ErrorEvent wrapping TextlintError does not work for me.

スクリーンショット 2023-06-20 021005

Copy link
Member

Choose a reason for hiding this comment

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

Fmm, It may be worker limitation.

image

We can not pass the details of the error in worker via error event.

Copy link
Contributor Author

@otariidae otariidae Jun 20, 2023

Choose a reason for hiding this comment

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

Thank you for your information.
Another option for less complex error handling I think is that all errors in the worker are handled through worker messages instead of throwing Error or calling reportError. It seems almost practically safe to omit handling of error and messaageerror. I finally understand why other libraries such as comlink have their own error message protocol.
If we want to promise more safety, worker can post error messages when the worker gets error or unhandledrejection on the worker side:

self.addEventListener('error', (error) => {
    // handling all unexpected error on the worker side and notify the client through message
    self.postMessage({
        command: 'error',
        error,
    })
})

Copy link
Member

Choose a reason for hiding this comment

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

another option for less complex error handling I think is that all errors in the worker are handled through worker messages instead of throwing Error or calling reportError

Yes. it is good that keep it simple.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

implemented in 1375633
Textlint worker handles all possible errors and post error messages. It simplified client-side error handling by omitting error and messageerror event

Comment on lines 148 to 153
}).catch(error => {
return self.postMessage({
id: data.id,
command: "error",
error
});
Copy link
Member

@azu azu Jun 19, 2023

Choose a reason for hiding this comment

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

Probably, reportError allow to report ErrorEvent which include custom Error object.

const error = new Error('description');
error.id = "test-1";
self.reportError(new ErrorEvent('textlint error', {
    error : error,
    message : 'description',
}));
image

Technically, it could be written as follows.

class LintError extends Error { }
lintText(...).catch(error => {
  reportError(new ErrorEvent('textlint error', {
    error : new LintError("description", { id }),
    message : 'description',
}))

However, Safari/Node.js/Deno does not support reportError in WebWorker yet.
https://developer.mozilla.org/en-US/docs/Web/API/reportError

Comment on lines 158 to 166
const results = await lintEngine.lintText({
text
});
let results;
try {
results = await lintEngine.lintText({
text
});
} catch (e) {
debug("lint error", e);
results = [] as const;
}
Copy link
Member

Choose a reason for hiding this comment

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

Errors should be handled by the caller.

update().catch(error => ...) or create some wrapper for logging.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

implemented in e181e81
But I am not so much familiar with layered error handling technique so please review if it may be wrong for you.

@azu azu added the Type: Feature New Feature label Jun 21, 2023
Comment on lines 123 to 126
fixPromise.finally(() => {
controller.abort();
});
return fixPromise;
Copy link
Member

Choose a reason for hiding this comment

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

Promise.prototype.finally return a Promise.
So you can return the promise.

Suggested change
fixPromise.finally(() => {
controller.abort();
});
return fixPromise;
return fixPromise.finally(() => {
controller.abort();
});;

Copy link
Contributor Author

Choose a reason for hiding this comment

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

implemented in 7ea7580
also removed Promise variable definition as it looks redundant too in this case

Comment on lines 82 to 85
lintPromise.finally(() => {
controller.abort();
});
return lintPromise;
Copy link
Member

Choose a reason for hiding this comment

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

same here.

Although in most cases there is no difference.

Copy link
Member

@azu azu left a comment

Choose a reason for hiding this comment

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

Almost LGTM.
Thanks for work!

@azu azu merged commit f7f6606 into textlint:master Jun 22, 2023
@azu
Copy link
Member

azu commented Jun 22, 2023

Thank you for your patience!
https://github.com/textlint/editor/releases/tag/v0.15.0 🎉

@otariidae otariidae deleted the error-handling-worker branch June 22, 2023 13:47
@otariidae
Copy link
Contributor Author

Thank you!
This PR was harder than I expected but is a very good experience for me.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Type: Feature New Feature
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants