-
-
Notifications
You must be signed in to change notification settings - Fork 297
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
add unwrap()
method to supabase client
#92
Comments
Out of curiosity, why do all the calls return an error instead of rejecting the Promise? This seems like a very unusual pattern for Javascript/Typescript. |
@emschwartz they are rejecting the promise. This is async/await syntax, which can use try/catch blocks to catch rejected promises |
They are? I'm familiar with async/await syntax but all of the examples here show const { data, error } = await supabase
.from('cities') As as result, it seems like we need to write code like this: try {
const { data, error } = await supabase
.from('cities')
if (error) {
throw error
}
} catch (err) {
// handle err
} If the Promise were rejected instead of returning the error object, you could leave out the (This is the line I would expect to throw instead of returning) |
I understand your confusion now. If you look at my first message, the error is being thrown in the "Describe the solution you'd like" section. This is my suggestion on how an |
Oh whoops, @andykais sorry for the misunderstanding. My question was actually about the library as it currently stands rather than your suggestion. I think your proposal seems like a reasonable way to improve the ergonomics without breaking backwards compatibility. If there is going to be a major version change with breaking changes, I would vote for the pattern to be changed entirely so that all errors cause the Promise to be rejected. |
Hi @emschwartz - this is just a DX decision. We purposely chose to return errors rather than throw them. There is no right or wrong here, only preferences. We decided to return because (we believe) it is a nicer developer experience :) . I'm sorry you don't share the view - I didn't either until I started using it, so perhaps this library will change your mind |
Hi @kiwicopple I hear that and could accept that it's just a different style choice if the Promise were guaranteed never to reject (because then you wouldn't need to wrap it in a try/catch). However, this call to All of that said, I'm using your open source library so take my comments with a grain of salt :) |
I agree with @emschwartz, we end up try-catch'ing + checking for error in the returned object. Edit: The auth part already does this (e.g signUp) |
I do second this feature request. Since a database call isn't usually the only thing that's happening, I've found that I've often needed to double catch errors. For instance: try {
const { data, error } = await supabase.from('table').select('*')
if (error) throw new Error(error)
// do other post-processing
} catch (error) {
// handle the errors
} This is a short example, but it can get especially cumbersome when combining this with other API calls for other services. Here's a contrived example that should hopefully get the point across. try {
const [{ data, error }, fetchResponse, otherApiResponse] = await Promise.all([
supabase.from('table').select('*'),
fetch('https://example.com/api-endpoint'),
someOtherApiFunctionCall(),
])
if (error) throw new Error(error)
// do other post-processing
} catch (error) {
// handle the errors
} Notice how the Supabase error is handled differently from everything else? Or what if you don't use async/await, and you use then/catch. For instance, inside of a react useEffect hook, then/catch is preferred since you cannot use async/await directly in hook. const [posts, setPosts] = useState()
const [errorMessage, setErrorMessage] = useState('')
// example with no error handling
useEffect(() => {
supabase
.from('posts')
.select('*')
.then(({ data }) => setPosts(data))
}, [])
// with error handling
useEffect(() => {
const getData = async () => {
const { data, error } = await supabase.from('posts').select('*')
if (error) setError(error)
else setPosts(data)
}
getData()
}, [])
// error handling if supabase unwrapped promises
useEffect(() => {
supabase
.from('posts')
.select('*')
.unwrap()
.then((data) => setPosts(data))
.catch((error) => setError(error))
}, []) I would argue that the unwrap option is the clearest option to read and write. I understand that this is a matter of opinion and not objective fact, but when a lot of JavaScript is built around being able to reject promises, I find it hard to believe that not rejecting on errors is always better. At least having the option with |
For what it's worth, the decision to return the error as a result rather than throw is described in #32. I opened a discussion about this some time ago asking pretty much the same thing as you, with a slightly (but not meaningfully) different take on it: supabase/supabase#604 We've found that we use both the try {
const data = await throwOnError(supabase ...)
} catch (error) {
...
} I'm gonna take a stab at a PR for this, because it's annoying me to no end. 😅 |
Proposed implementation available in supabase/postgrest-js#188. |
…hem (#188) * Implement unwrap() to throw query errors instead of returning them The rationale and motivation for this change is well described in supabase/supabase-js#92, and further supported by the discussion in supabase/supabase#604. This implementation doesn't actually unwrap the result as described in supabase/supabase-js#92, i.e. returning only the actual data. This is done deliberately for two reasons: 1. Making sure the caller can still extract `count`, `status` and `statusText` while still benefitting from errors being thrown instead of returned 2. Ease the transition from the non-throwing pattern where destructuring is norm anyway, so less code has to be rewritten to take advantage of unwrap Basic tests were added to test/basic.ts and the unwrap function is documented to some degree at least, though this can probably be improved. * Rename the unwrap API to throwOnError instead The `unwrap` moniker was a bit of a misnomer, since it didn't actually unwrap the result, and this should make things a bit more clear. This was discussed in a bit more detail in #188.
I guess supabase/postgrest-js#188 doesn't technically fit the definition of the unwrap pattern described here, but more or less solves the problem anyway? Is it enough to close this issue, @andykais? |
I would say it definitely covers my initial use case @mstade. Hats off to you! Thanks for putting in the time. |
Happy to help! 😊 |
OMG i just discovered that now, felt so annoyed by that as well, i tried to unwrap in SDK v2, but method seems not available how to use it in V2? |
Ok found it |
Feature request
add an
unwrap()
method or a an either monadleft()
method to pull the data out of a postgrest response.Is your feature request related to a problem? Please describe.
Not exactly a problem, but the way the client is designed requires every database query to individually handle database failures. For the most part, I want to assume that the query succeeded, and throw an error if it did not. That makes most of my db calls look like this:
Describe the solution you'd like
The solution I would like best, would be to add a method which can optionally unwrap the
{ error: object } | { data: object[] }
response from supabase into just the data field. If there is an error present, the client should throw an error. This should not break any existing workflows, its just an optional convenience method.Usage:
Describe alternatives you've considered
I have attempted building this on top of the supabase client, but because of the way queries are built, I need to use proxies:
This technically works, but manipulating the types to include the
unwrap()
method is harder.Additional context
unwrap
is a common pattern in several languages & libraries. Here is an example in rust: https://doc.rust-lang.org/rust-by-example/error/option_unwrap.htmlThe text was updated successfully, but these errors were encountered: