Chaining apollo retryLink and errorLink - apollo-client

Should Apollo retryLink come before or after the errorLink? Some examples show it as before https://medium.com/#joanvila/productionizing-apollo-links-4cdc11d278eb#3249 while some show it after https://www.apollographql.com/docs/react/api/link/apollo-link-rest/#link-order.

It depends how you want your errorLink logic to work, from the docs
Additive composition involves combining a set of links into a serially executed chain
and the Error Link
is called after the GraphQL operation completes and execution is moving back up your link chain
so if you place the retryLink before the errorLink
ApolloLink.from([retryLink, errorLink])
the errorLink will also be executed with the retries once the respective result is traveling back up the chain, in other words (if you are using the default docs example), there should be a console log on every attempt and attempts.max console logs in total.
In case the order is flipped the errorLink will be called after the retryLink is complete and the result traveled all the way back up, so there should be 1 console log from the errorLink call.

Related

Project reactor - react to timeout happened downstream

Project Reactor has a variety of timeout() operators.
The very basic implementation raises TimeoutException in case no item arrives within the given Duration. The exception is propagated downstream , and to upstream it sends cancel signal.
Basically my question is: is it possible to somehow react (and do something) specifically to timeout that happened downstream, not just to cancelation that sent after timeout happened?
My question is based on the requirements of my real business case and also I'm wondering if there is a straight solution.
I'll simplify my code for better understanding what I want to achieve.
Let's say I have the following reactive pipeline:
Flux.fromIterable(List.of(firstClient, secondClient))
.concatMap(Client::callApi) // making API calls sequentially
.collectList() // collecting results of API calls for further processing
.timeout(Duration.ofMillis(3000)) // the entire process should not take more than duration specified
.subscribe();
I have multiple clients for making API calls. The business requirement is to call them sequantilly, so I call them with concatMap(). Then I should collect all the results and the entire process should not take more than some Duration
The Client interface:
interface Client {
Mono<Result> callApi();
}
And the implementations:
Client firstClient = () ->
Mono.delay(Duration.ofMillis(2000L)) // simulating delay of first api call
.map(__ -> new Result())
// !!! Pseudo-operator just to demonstrate what I want to achieve
.doOnTimeoutDownstream(() ->
log.info("First API call canceled due to downstream timeout!")
);
Client secondClient = () ->
Mono.delay(Duration.ofMillis(1500L)) // simulating delay of second api call
.map(__ -> new Result())
// !!! Pseudo-operator just to demonstrate what I want to achieve
.doOnTimeoutDownstream(() ->
log.info("Second API call canceled due to downstream timeout!")
);
So, if I have not received and collected all the results during the amount of time specified, I need to know which API call was actually canceled due to downstream timeout and have some callback for this "event".
I know I could put doOnCancel() callback to every client call (instead of pseudo-operator I demonstrated) and it would work, but this callback reacts to cancelation, which may happen due to any error.
Of course, with proper exception handling (onErrorResume(), for example) it would work as I expect, however, I'm interesting if there is some straight way to somehow react specifically to timeout in this case.

Cypress async form validation - how to capture (possibly) quick state changes

I have some async form validation code that I'd like to put under test using Cypress. The code is pretty simple -
on user input, enter async validation UI state (or stay in that state if there are previous validation requests that haven't been responded to)
send a request to the server
receive a response
if there are no pending requests, leave async validation UI state
Step 1 is the part I want to test. Right now, this means checking if some element has been assigned some class -- but the state changes can happen very fast, and most of the time (not always!) Cypress times out waiting for something that has ALREADY happened (in other words, step 4 has already occurred by the time we get around to seeing if step 1 happened).
So the failing test looks like:
cy.get("#some-input").type("...");
cy.get("#some-target-element").should("have.class", "class-to-check-for");
Usually, by the time Cypress gets to the second line, step 4 has already ran and the test fails. Is there a common pattern I should know about to solve this? I would naturally prefer not to have change the code under test.
Edit 1:
I'm not certain that I've 100% solved the "race" condition here, but if I use the underlying native elements (discarding the jQuery abstraction), I haven't had a failure yet.
So, changing:
cy.get("#some-input").type("...")
to:
cy.get("#some-input").then(jQueryObj => {
let nativeElement = jQueryObj[0];
nativeElement.value = "...";
nativeElement.dispatchEvent(new Event("input")); // make sure the app knows this element changed
});
And then running Cypress' checks for what classes have / haven't been added has been effective.
You can stub the server request that happens during form validation - and slow it down, see delay parameter https://docs.cypress.io/api/commands/route.html#Use-delays-for-responses
While the request is delayed, your app's validation UI is showing, you can validate it and then once the request finishes, check if the UI goes away.

Rxjs - How to retry an errored observable while informing UI of the error

Problem
Suppose there is a Http request observable that errored, we can just retry it. But I also want the UI to inform the user that this resource failed to load. What is the best architecture?
Intended Behavior for the Target Observable
Retry-able.
Long-running. Doesn't complete or error.
Shared. Does not generate unnecessary requests when multiple subscriber.
Load on need. Does not generate unnecessary requests when not subscribed.
Inform UI of the errors.
(3 and 4 can be achieved by shareReplay({bufferSize: 1, refCount: true}))
My Attempts
I think it's best to pass an error message to the downstream observer while keeping retrying the source. It causes minimum changes to the architecture. But I didn't see a way I can do it with Rxjs, because
retry() always intercepts the error. If you materialze the error, then retry() won't retry. If not, then no error will propagate to the downstream.
catchError() without rethrowing will always complete the stream.
Although let the UI observer tap(,,onError) and retry() can satisfy this need, but I think it is dangerous to let the UI take this responsibility. And multiple UI observer means a LOT of duplicated retries.
Well, I seem to have accidentally find the answer while browsing through the documentations.
It starts with the usage of the second parameter of the catchError. According to the documentation, retry is implemented by catchError. And we can express more logic with the lower-level catchError.
So it's just
catchError((err, caught) => {
return timer(RETRY_DELAY_TIME).pipe(
mergeMap(() => caught)
startWith(err)
);
})
It retries the observable, meanwhile sending error messages to the downstream observers. So the downstream is aware of the connection error, and can expect to receive retried values.
It sounds like you're looking for something akin to an NgRx side effect. You can encase it all in an outer Observable, piping the error handler to the inner Observable (your HTTP call), something like this:
const myObs$ = fromEvent('place event that triggers call here').pipe(
// just one example, you can trigger this as you please
switchMap(() => this.myHttpService.getResource().pipe(
catchError(err => handleAndRethrowError()),
retry(3)
),
shareReplay()
);
This way, if the request throws an error, it is retried 3 times (with error handling in the catchError block, and even if it fully errors out, the outer Observable is still alive. Does that look like it makes sense?

Terminating vs non-terminating link apollo link?

const httpLink = ...
const errorLink = ...
const link = ApolloLink.from([errorLink, httpLink]);
When doing so, be careful to chain the control flow in the correct
order. The apollo-link-http is called a terminating link because it
turns an operation into a result that usually occurs from a network
request. On the other side, the apollo-link-error is a non-terminating
link. It only enhances your terminating link with features, since a
terminating link has to be last entity in the control flow chain.
So here I didn't get the difference between terminating link and non-terminating link??
source: https://www.robinwieruch.de/react-graphql-apollo-tutorial/
"Simply enough, the terminating link is the one that doesn't use the forward argument, but instead turns the operation into the result directly." as stated at: https://www.apollographql.com/docs/link/overview/
In short, your link should either pass to next link in chain by invoking forward (non-terminating) or return some data (terminating) (non-terminating).

Angular.JS multiple $http post: canceling if one fails

I am new to angular and want to use it to send data to my app's backend. In several occasions, I have to make several http post calls that should either all succeed or all fail. This is the scenario that's causing me a headache: given two http post calls, what if one call succeeds, but the other fails? This will lead to inconsistencies in the database. I want to know if there's a way to cancel the succeeding calls if at least one call has failed. Thanks!
Without knowing more about your specific situation I would urge you to use the promise error handling if you are not already doing so. There's only one situation that I know you can cancel a promise that has been sent is by using the timeout option in the $http(look at this SO post), but you can definitely prevent future requests. What happens when you make a $http call is that it returns a promise object(look at $q here). What this does is it returns two methods that you can chain on your $http request called success and failure so it looks like $http.success({...stuff...}).error({...more stuff..}). So if you do have error handling in each of these scenarios and you get a .error, dont make the next call.
You can cancel the next requests in the chain, but the previous ones have already been sent. You need to provide the necessary backend functionality to reverse them.
If every step is dependent on the other and causes changes in your database, it might be better to do the whole process in the backend, triggered by a single "POST" request. I think it is easier to model this process synchronously, and that is easier to do in the server than in the client.
However, if you must do the post requests in the client side, you could define each request step as a separate function, and chain them via then(successCallback, errorCallback) (Nice video example here: https://egghead.io/lessons/angularjs-chained-promises).
In your case, at each step you can check if the previous one failed an take action to reverse it by using the error callback of then:
var firstStep = function(initialData){
return $http.post('/some/url', data).then(function(dataFromServer){
// Do something with the data
return {
dataNeededByNextStep: processedData,
dataNeededToReverseThisStep: moreData
}
});
};
var secondStep = function(dataFromPreviousStep){
return $http.post('/some/other/url', data).then(function(dataFromServer){
// Do something with the data
return {
dataNeededByNextStep: processedData,
dataNeededToReverseThisStep: moreData
}
}, function(){
// On error
reversePreviousStep(dataFromPreviousStep.dataNeededToReverseThisStep);
});
};
var thirdFunction = function(){ ... };
...
firstFunction(initialData).then(secondFunction)
.then(thirdFunction)
...
If any of the steps in the chain fails, it's promise would fail, and next steps will not be executed.

Resources