Skip to content
This repository has been archived by the owner on Apr 14, 2023. It is now read-only.

How to handle async requests? #137

Closed
Nickersoft opened this issue Oct 9, 2017 · 3 comments
Closed

How to handle async requests? #137

Nickersoft opened this issue Oct 9, 2017 · 3 comments

Comments

@Nickersoft
Copy link

Hi guys, excited for the upcoming release of Apollo Client 2.0! I'm trying to migrate over to the beta, and am having something difficulty setting up my auth middleware. My auth token is stored in my RN app via AsyncStorage, which (you guessed it), is asynchronous. Previously this wasn't an issue, as this was the middleware code I was using to inject the auth token into the header:

const authMiddleware = {
    async applyMiddleware(req, next) {
        if (!req.options.headers)
            // Create the header object if needed.
            req.options.headers = {};

        // Inject the auth header if it exists
        const authExists = await Storage.authToken.exists();

        if (authExists) {
            const token = await Storage.authToken.get();
            req.options.headers['Authorization'] = `Bearer ${token}`;
        }

        next();
    }
};

Now I've switched over to the new Link implementation, as documented here:

const authLink = new ApolloLink(async (operation, forward) => {
    const authExists = await Storage.authToken.exists();

    if (authExists) {
        const token = await Storage.authToken.get();

        operation.setContext({
            headers: {
                Authorization: `Bearer ${token}`
            }
        });
    }

    return forward(operation);
});

The only problem seems to be that the new library doesn't seem to like the async keyword. When I remove it and comment out all but the return statement, I get (as one would expect) a 403 error from my server. However, with the keyword added I get:

Network error: undefined is not a function (near '..._this.inFlightRequestObservables[key].subscribe...')

I imagine this has to do with the fact the request object is returning a promise instead of its expected value. Is there any approach that could remedy this?

Intended outcome:

The app should apply middleware despite the use of promises.

Actual outcome:

The above error is thrown if the async keyword is present.. even without it.. you'd still need to return the forward() call inside a then() method, which wouldn't work.

How to reproduce the issue:

I believe adding async to the request function of any ApolloLink constructor call would do it.

@Nickersoft
Copy link
Author

Nickersoft commented Oct 10, 2017

So with enough tinkering I was able to actually come up with a fix myself:

const authLink = new ApolloLink((operation, forward) => {
    return new Observable(observable => {
        let sub = null;

        Storage.authToken.exists().then(exists => {
            if (exists) {
                Storage.authToken.get().then(token => {
                    operation.setContext({
                        headers: {
                            Authorization: `Bearer ${token}`
                        }
                    });

                    sub = forward(operation).subscribe(observable);
                });
            } else {
                sub = forward(operation).subscribe(observable);
            }
        });

        return () => (sub ? sub.unsubscribe() : null);
    });
});

That said, I'm very new to this library so I'm hoping my implementation doesn't open the door to any vulnerabilities. I think that because sub is only being referenced and not copied, the null value will resolve to the proper subscription once the Promise resolves. Anyway, closing for now. Hopefully this code helps someone.

@lnikkila
Copy link

For anyone searching, apollo-link-context is now a thing:

https://github.com/apollographql/apollo-link/blob/5fbaaf47e5ff1189d65ee674f09fbda464c23d5a/packages/apollo-link-context/README.md

@tazsingh
Copy link

I got the same error however without async on the constructor, but async on the Observable's constructor. You can replicate it as follows:

new ApolloLink(() => {
  return new Observable(async observer => {
    await new Promise((resolve, reject) => setTimeout(resolve, 1000))

    observer.next({data: {}})
    observer.complete()
  })
})

Or the most minimal step to replicate:

new ApolloLink(() => {
  return new Observable(() => {
    return new Promise()
  })
})

It's very odd as if I unroll the async/await as follows:

new ApolloLink(() => {
  return new Observable(observer => {
    const timeoutPromise = new Promise((resolve, reject) => setTimeout(resolve, 1000))

    timeoutPromise.then(() => {
      observer.next({data: {}})
      observer.complete()
    })
  })
})

Then all is well.

It seems that the Observable library does not like AsyncFunctions (which are essentially the same as a returned Promise)? I would not have guessed this as it shouldn't matter what the constructor returns as long as observer.{next/complete/error/etc} are called within the constructor accordingly.

Let me know if you require any additional information. Happy to open a separate issue to track this as well.

Thanks!

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

No branches or pull requests

3 participants