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

withFilter's filterFn is called with a null payload #132

Closed
green-coder opened this issue Feb 9, 2018 · 9 comments
Closed

withFilter's filterFn is called with a null payload #132

green-coder opened this issue Feb 9, 2018 · 9 comments

Comments

@green-coder
Copy link
Contributor

green-coder commented Feb 9, 2018

withFilter()'s filterFn is called with a null payload when it should not even be called.

I strongly suspect (because I took a look at its source code) that it is called when the async iterator is returning an object that says that there is no more values.

    const getNextPromise = () => {
      return asyncIterator
        .next()
        .then(payload => Promise.all([
          payload,
          Promise.resolve(filterFn(payload.value, args, context, info)).catch(() => false),
        ]))
        .then(([payload, filterResult]) => {
          if (filterResult === true || payload.done === true) {
            return payload;
          }

          // Skip the current value and wait for the next one
          return getNextPromise();
        });
    };

That's wrong, the filter function should not be called when there is no more value in the iterator.

payload.done === true should be tested before calling filterFn.

@green-coder
Copy link
Contributor Author

green-coder commented Feb 9, 2018

I would like to point out that the last object sent by an async iterator is normally {value: undefined, done: true}, it does not convey the last item of the iteration.

green-coder referenced this issue Feb 9, 2018
Fix Issue #81 Infinite loop after last unsubscribe in withFilter function
@NeoPhi
Copy link
Contributor

NeoPhi commented Feb 22, 2018

This is tricky since the JS specification for iterators allows a value to be included even when done == true.

From iterator protocol
Has the value true if the iterator is past the end of the iterated sequence. In this case value optionally specifies the return value of the iterator.

@green-coder
Copy link
Contributor Author

green-coder commented Feb 22, 2018

Not so tricky : currently, the filter is called with an undefined value. This is a bug.

The fix could be adapted to test for an undefined value, but the best is to make sure not to use the ambiguity of the protocol. What if the data's last element is an undefined value, how would you know if there is a data or not?

@green-coder
Copy link
Contributor Author

Okay .. maybe one could test if the key value is present in the object.

@green-coder
Copy link
Contributor Author

I will add another commit to take into account the information you gave and handle the special case you mentioned.

@grantwwu
Copy link
Contributor

I suspect that the root cause is completely different. https://github.com/apollographql/graphql-subscriptions/blob/master/src/with-filter.ts#L13 we are getting payload.value before we even await payload. payload.value would end up being undefined, always.

grantwwu pushed a commit that referenced this issue Nov 13, 2018
The filterFn is no longer called after there is no more items to iterate on.
@grantwwu
Copy link
Contributor

I reviewed the code and it LGTM. Rebased and merged.

@grantwwu
Copy link
Contributor

Okay, so I was wrong. payload at this line

is not a Promise - but that's okay - Promise.all actually just silently ignores non-promise values in the iterable argument it supplies.

Still - I believe the fix is correct - the old version of the code unconditionally calls the filterFn - even when payload.value is undefined.

[done] has the value true if the iterator is past the end of the iterated sequence. In this case value optionally specifies the return value of the iterator.

It's not actually clear to me that it makes sense to apply the filterFn to the return value of the iterator. (what does that even mean, exactly?) I would think that we should only be applying it to elements yielded by the iterator?

@NeoPhi Thanks for catching this - what do you think?

@grantwwu grantwwu reopened this Nov 19, 2018
@NeoPhi
Copy link
Contributor

NeoPhi commented Nov 19, 2018

In looking at how the output of this consumed https://github.com/graphql/graphql-js/blob/master/src/subscription/mapAsyncIterator.js#L35 it doesn't look like the calling code considers what happens if a value is set when done is set. Given that the change to check and quick bail when done is set makes sense, even if not 100% accounting for how the JS spec is written.

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

No branches or pull requests

3 participants