-
Notifications
You must be signed in to change notification settings - Fork 662
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
WebAPICallResult and the error handling is not intuitive #775
Comments
hi @lenkan, thanks for your keen insights, and taking the time to write them up for us to benefit.
While this is true in success cases, that's not the only place where node-slack-sdk/packages/web-api/src/errors.ts Lines 24 to 29 in e754103
The relevant part is that the This structure was chosen because its possible that while Slack is telling you the request was not successful, there may be more data available in the body to tell you why it wasn't successful. It's important that we give you a way to access that the data Slack has sent back, even when there's an error; hence, the While your observation is true, that the
This is an interesting point of view. At a coarse level, the assumption is that most developers care about whether something succeeded or not, which Promises are good at expressing as resolve or reject. We've taken the design approach to separate the reasons something didn't succeed using the |
Thanks for your comments. I can see your point of view more clearly.
Let me first say that this is not a big issue at all. It does not "get in the way", it is just unexpected to me, it guides me to implement code that does not work as expected. I will try to explain with an example. I am new to using the Slack API, and had implemented an application to simply post notifications to a particular channel. Essentially something like this:
Here I see that I also expect this value to be one of the values as indicated by the API documentation. So, I might go ahead and do something like this:
Or I might go ahead and do this if I really don't care if the posting succeeds.
The important point being that the interface of the return value communicates that the Obviously the code is simply redundant, because there are no circumstances where the In the above example, I didn't really need to handle different error cases. It is true that in most use cases, all errors are handled equally, by throwing. Therefore I didn't really pay much attention at this point, it simply threw anyways. One week later I decided to also do a
In my opinion, this sort of error handling can be quite powerful, especially in typescript. You could explicitly type each methods possible error values. Which makes it dead-easy to explore the API using an intellisense enabled editor. From there, I can investigate which error cases that I should handle, and which cases I should simply throw. There might be some if-statements, that is true, but in the simplest of cases, it would be handled with an Let's look at how the above error must be handled currently instead:
I find this way of handling the error cases to be much more verbose and error prone. I do not automatically get typings for a caught I guess my point is two-fold. I do like what the interface From your explanation I do see that the property is used internally, in the typing of the error. So yes, perhaps it should simply be separated.
Maybe this is the issue, it would be more communicative to "rebrand" this interface without the error property before being used in the public API as a return value. An interface like this:
would communicate to me that the error is handled internally, and when this function returns a promise that resolves, it was successful. Therefore I would immediately handle the error correctly. In an ideal world, I would expect a return value similar to this:
where a set Edit: Which is what you mentioned too if you were to redesign it. |
Just to echo what was already said (and somewhat agreed on), imagine seeing this in your editor: Seeing this for the first time (or even just looking past it for the 100th time), the word "error" just jumps out at me. 🤔 From a strict perspective, the current exposed type of
Overloads like ...Yet the exported type for It's true, the
Then why is the signature for 🙂 Let me know if I've missed something while thinking about this. @lenkan I cannot deny that your /** Includes Web API platform errors as resolved values. */
async function withPlatformError(p: Promise<WebAPICallResult>): Promise<WebAPICallResult & { ok: boolean; error?: string; }> {
try {
// pass successes through
return await p;
} catch (err) {
if (err.code === 'slack_webapi_platform_error' && err.data !== undefined) {
// resolve Web API Platform errors
resolve(err.data);
} else {
// reject other errors
reject(err);
}
}
} (Note: untested.) The usage would be something like your original (clean) function: async function findUserId(email) {
// throws for HTTP errors (and such), but not for Web API errors
const result = await withPlatformError(client.users.lookupByEmail({ email }));
if(result.error === 'users_not_found') {
return undefined;
}
if (result.error) { // other API errors
throw new Error(result.error);
}
return result.user.id;
} I can't think of a good way this might be implemented into the I'd also be concerned about unhandled errors. Maybe a async function findUserId(email) {
// "map some platform errors"
const result = await mapPlatformErrors(
client.users.lookupByEmail({ email }),
// maps a `users_not_found` error to return `undefined`;
// throws for all other platform errors
{ 'users_not_found': undefined },
);
return result && result.user.id;
} If we ever get optional chaining, then this gets even shorter and sweeter: const findUserId = async (email) => await mapPlatformErrors(
client.users.lookupByEmail({ email }),
{ 'users_not_found': undefined },
)?.user.id; But I'm just dreaming now. 😊 |
I'd like to chime in here that it would be useful to include more info in the Here's an example error
This gives no clues about what the channel is or what the request parameters were/request method being used. Here's what I think would help: export interface WebAPIPlatformError extends CodedError {
code: ErrorCode.PlatformError;
data: WebAPICallResult & {
error: string;
};
method?: string;
options?: WebAPICallOptions;
} Here's an example: main...oligriffiths:oli/error-options that produces:
Thoughts? |
👋 It looks like this issue has been open for 30 days with no activity. We'll mark this as stale for now, and wait 10 days for an update or for further comment before closing this issue out. |
After many years of inactivity here, I am reviewing the typing of the Using the original poster's example of calling
Additionally, the error will have a structure like so:
.. while a successful call's result will look like:
IMHO, the root of this issue is that, in the rejection/error case, we "wrap" the API result in an error object and nest the API result under a However, I could easily see the other side, too: because an exception is thrown if the API result is not successful, TypeScript offers no type guarantees for exceptions (AFAICT exceptions are all of type Ultimately, any change we decide on to improve the developer experience for this issue would be a breaking change, so would not land until (at least) v8 of
|
Description
This is not really a bug, but rather a feature of the API that caught me off guard. I think there is room for improvement for how errors are propagated back to the user from the WebClient. See "Steps to reproduce" for an explanation of what surprised me.
What type of issue is this? (place an
x
in one of the[ ]
)Requirements (place an
x
in each of the[ ]
)Bug Report
Filling out the following details about bugs will help us solve your issue sooner.
Packages:
Select all that apply:
@slack/web-api
@slack/events-api
@slack/interactive-messages
@slack/rtm-api
@slack/webhooks
Reproducible in:
package version: 4.12.0
node version: 10.15.3
OS version(s): Arch Linux 5.0.7
Steps to reproduce:
users.lookupByEmail
for a team where the email is not registered.Expected result:
Since the interface
WepAPICallResult
has the following shape, in particular theerror
andok
properties:I expect that the call to
lookupByEmail
should resolve to an object similar to:I.e., the
error
property indicates that there was an error executing the particular lookup.Actual result:
The call rejected to an Error object containing:
Discussion:
I believe the simplest solution would be to simply not include
error
in the interfaceWebAPICallResult
. This would change the public interface as it is communicated, but not how it actually works (?).As far as I can see from the source, the returned value never specifies a value for this property. When the client receives a non-OK HTTP response, it simply wraps this into an
Error
and throw instead. Please correct me if I am mistaking.With that being said, I do not really see the value of rejecting the API-call unless there actually is an unexpected Error, such as a network error or a bug. That way, we can simply check the
response.error
orresponse.ok
property in our application code and act accordingly. But that would obviously be a breaking change.The text was updated successfully, but these errors were encountered: