Skip to content

Feature: Timeout, Retry, and Queue #92

@niftylettuce

Description

@niftylettuce

I'm writing my thoughts down here, because I do not have the time right now to dedicate to adding this, however hopefully someone in the community might be able to. I'm closing #66, #69, #73 in favor of this single issue.

cc @kasbah @akmjenkins @davidgovea

Retry

We should not retry on all 500 status error codes as every other fetch retry package does - instead we should retry only based off of it isRetryAllowed(err) returns true by using is-retry-allowed OR if the response had a 429, 502, 503, or 504 status code.

We should also only retry on GET, PUT, HEAD, DELETE, OPTIONS, or TRACE methods. Our approach should also respect the Retry-After response header (see https://github.com/sindresorhus/got/blob/6eaa81ba8db87340bbdbd79ef91594803f37b854/source/normalize-arguments.ts#L231-L241 for inspiration.

In general our approach will be very similar to got and ky (e.g. https://github.com/sindresorhus/ky).

It should be up to the application layer to retry if a successful server response status code occurs, and it should not be fetch nor frisbee responsibility to retry based off a server response (which is why we will not use packages like fetch-retry-or-die, make-fetch-happen, nor fetch-retry (because all of these implementations retry on 5xx status codes and none of them respect the actual error code). If a server responds, it's considered to be successful.

Finally, if someone aborts a Frisbee fetch request using AbortController, then the retries should be aborted as well and it should not retry from an AbortError. This should happen automatically out of the box, but I wanted to write it down. The best way to detect abort errors is probably the same as we do with tests, via err.name === 'AbortError' (see https://github.com/niftylettuce/frisbee/blob/master/test/node.test.js#L334).

I think that we can use the p-retry package and allow a global retry option via new Frisbee({ retry: { ... } }), and for individual API methods such as frisbee.get('/', { retry: { ... } }) as well. If an individual API method specifies a retry object, then p-retry should use that, otherwise if a global option was passed it will use it. There are several other packages that are very similar (most of which use node-retry as well), however I trust @sindresorhus and his dedication to open source maintenance and quality, therefore I would use p-retry above all.

We should also respect the Retry-After header per https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Retry-After for 429 status codes.

Timeout

A timeout event should cause a retry, therefore we need to make sure the error we throw on a timeout (namely its code) returns true from isRetryAllowed(err). To properly design this, we should either use an existing AbortController passed, or create a new AbortController for each request. If the timeout is triggered, then it would signal an abort with an error created via const error = new Error('ETIMEDOUT'); error.code = 'ETIMEDOUT'; (similar to how request does it here https://github.com/request/request/blob/df346d8531ac4b8c360df301f228d5767d0e374e/request.js#L848-L849). By using AbortController, we can ensure that the fetch attempted is actually aborted, and will not be completed/return a response after the timeout error occurs.

Unfortunately we cannot use any existing fetch timeout packages because all of them polyfill or have odd implementations of detecting which fetch implementation to use, therefore we have to write this ourselves.

We can most likely use p-timeout for this.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions