morrys / wora

Write Once, Render Anywhere. typescript libraries: cache-persist, apollo-offline, relay-offline, offline-first, apollo-cache, relay-store, netinfo, detect-network
https://morrys.github.io/wora/docs/introduction
MIT License
174 stars 4 forks source link

Stop processing queue at the middle of the process #128

Closed gtkatakura closed 1 year ago

gtkatakura commented 1 year ago

Do we have an recommend way to stop to process an queue at the middle of the process and keep them at the queue? Like, today we have an middlware at our project which try to refresh the authentication token, but you have a limit time so your token can expire and you gonna need to logout. To us, after the first mutation failure because of the refresh token failure, we know that the rest don't gonna work anyway and we want to keep all these mutations at the queue. Today I achieve it with this configuration:

  let stopToProcessQueue = false

  environment.setOfflineOptions({
    start: (mutations) => {
      stopToProcessQueue = false

      return Promise.resolve(mutations)
    },
    onExecute: (mutation) => {
      if (stopToProcessQueue) {
        // eslint-disable-next-line @typescript-eslint/no-explicit-any
        return Promise.resolve(undefined) as any
      }

      return Promise.resolve(mutation)
    },
    onDiscard: (options) => {
      if (options.error instanceof RefreshTokenError) {
        stopToProcessQueue = true

        return Promise.resolve(false)
      }

      return Promise.resolve(true)
    },
  })

This works because the current implementation at offline-first/src/index.ts:

                        const processMutation = await onExecute(mutation);
                        if (processMutation) {
                            if (!processMutation.serial) {
                                parallelPromises.push(this.executeMutation(processMutation));
                            } else {
                                await Promise.all(parallelPromises)
                                    .then(() => this.executeMutation(processMutation))
                                    .catch((error) => {
                                        throw error;
                                    });
                                parallelPromises = [];
                            }
                        }

But you can see that I am using eslint-disable-next-line @typescript-eslint/no-explicit-any at my code, because the onExecute type doesn't support to return undefined | null, so I am not sure if this implementation gonna keep safe at new releases of the project.

Do you think that this use case makes sense? If it does, I gonna open an PR to update the type of the onExecute function.

morrys commented 1 year ago

@gtkatakura I am not convinced of the solution and to change the onExecute method ... Wouldn't it be better to manage it in the network? If you do not want to execute the request to the server, throw the error and handle the error in the onDiscard

gtkatakura commented 1 year ago

My network layer has several configurations so I have one file for the network and other for the offline configurations. This was my first solution, but this way I need some file/module scope state. Something like:

const withRelayListenerMiddleware: Middleware = (next) => async (...args) => {
  try {
    if (stopToProcessQueue) {
      throw new SkippedRequestError()
    }
gtkatakura commented 1 year ago

The main question is that both solutions sounds weird to me, like I already know that I should stop to process the queue, but the queue keeping executing just to throws errors which I already know that gonna happen.

It is like having a lot of events on the queue (20-30), so the internet goes back, you process like 10 events, if the internet goes down at the middle of the process, shouldn't it stop to trying to process the rest of the queue?

If it should, like the internet is as constraint to keeping executing the queue, would be nice to have an way to control if the queue should stop the current process by other ways.

morrys commented 1 year ago

Have you tried throwing an error in the onExecute method?

gtkatakura commented 1 year ago

It worked, oh, so sorry. I was so depth on this problem that at some moment I read the code inside executeMutation and I thought that this could capture the onExecute failure, I don't know why :rofl:

Thank you!