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

Make iteration query methods respect returned promises #1112

Merged
merged 4 commits into from
Mar 11, 2020

Conversation

noahsilas
Copy link
Contributor

These methods (ParseQuery.map, ParseQuery.filter, and ParseQuery.reduce) are documented as behaving similarly to ParseQuery.each (which they rely on under the hood): if the callback they are passed returns a promise, we should:

  • wait for that promise to resolve before continuing iteration
  • if that promise rejects, we should stop iteration and return a rejected promise

These methods weren't living up to that contract. Here's a series of adjustments to each one so that they follow those guidelines.

This function is documented as allowing the callback to return a
promise, and when the callback returns a promise we should wait for that
promise to resolve before continuing, and stop iteration if the promise
rejects.

If the callback returned a promise, the previous implementation would
return an array of promises, and run the callback on all of them. The
new implementation wraps the returned value in `Promise.resolve`,
coercing it into a promise, so that we can safely extract a value from
it before pushing the value into the results array. Returning this
promise in the `.each()` callback also provides the behavior of waiting
/ stopping based on the resolution of a promise from the callback.
This function is documented as allowing the callback to return a
promise, and when the callback returns a promise we should wait for that
promise to resolve before continuing, and stop iteration if the promise
rejects.

The previous implementation was a bit naive in that it would load the
entire set of objects matching the query into memory, and then call
`.reduce()` on it. The new implementation avoids this by applying the
callback on each object in turn. This lets us then return the result of
the callback to the `.each()` call, which handles waiting for promise
resolution / rejection.

Things here get a little tricky in that the behavior of `reduce` is
special when no initial value is provided: we take the first item, don't
pass it into the callback, and treat that item as the initial value. We
need to keep that treatment, which entails a check for a special case:
we're looking at the first item AND the initial value was undefined.
This function is documented as allowing the callback to return a
promise, and when the callback returns a promise we should wait for that
promise to resolve before continuing, and stop iteration if the promise
rejects.

When the callback returns a promise, the old behavior would have been to
push the object into the results array, because a promise is a truthy
value. Now we wait for the promise to resolve before checking the value.
@codecov
Copy link

codecov bot commented Mar 11, 2020

Codecov Report

Merging #1112 into master will increase coverage by 0.01%.
The diff coverage is 100%.

Impacted file tree graph

@@            Coverage Diff             @@
##           master    #1112      +/-   ##
==========================================
+ Coverage   92.24%   92.25%   +0.01%     
==========================================
  Files          54       54              
  Lines        5222     5232      +10     
  Branches     1170     1172       +2     
==========================================
+ Hits         4817     4827      +10     
  Misses        405      405
Impacted Files Coverage Δ
src/ParseQuery.js 96.03% <100%> (+0.06%) ⬆️

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update fb474a2...687e795. Read the comment docs.

Copy link
Contributor

@acinader acinader left a comment

Choose a reason for hiding this comment

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

This is super nice Noah. Thank you. I have a single question, which is inline.

src/ParseQuery.js Show resolved Hide resolved
This more closely mirrors the behavior of Array.reduce(), which throws a
TypeError in this situation.

Implementation notes:
- We return a rejected promise instead of throwing synchronously,
  because we need to wait for the result set to materialize before we
  know if you're reducing on an empty set

- The `.each()` callback is never invoked for an empty result set, so we
  need to have this empty check happen _after_ we've finished iteration.
  Fortunately we're already keeping track of the iteration index in this
  scope, so we can reuse that for this check.

Thanks for the feedback @acinader!
Copy link
Contributor

@acinader acinader left a comment

Choose a reason for hiding this comment

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

@noahsilas this is really great. Thank you! Hope you'll find other ways to contribute :).

@acinader acinader merged commit bc9d6af into parse-community:master Mar 11, 2020
@santiagosemhan
Copy link

@TomWFox Is this documented?

@dplewis
Copy link
Member

dplewis commented Mar 30, 2020

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

Successfully merging this pull request may close these issues.

4 participants