elbywan / wretch

A tiny wrapper built around fetch with an intuitive syntax. :candy:
MIT License
4.79k stars 96 forks source link

Add cache & throttle support #12

Closed chimon2000 closed 6 years ago

chimon2000 commented 6 years ago

Taking some inspiration from axios. It would be nice to have middleware for caching and throttling requests.

elbywan commented 6 years ago

@chimon2000 There is actually a pretty easy way to replicate middlewares with wretch using polyfills :

EDIT : I improved the code samples in the linked commit, below is a really naïve cache implementation

/* A polyfill function can be used as a middleware like this. */

// Polyfill signature
// (url: the url, options: standard fetch options) => a fetch response

// Middleware signature
// (... middleware options, next: the next middleware to call or global fetch to end the chain) => Polyfill

/* A simple delay */
const delayFetch = (delay, next = fetch) => (url, opts) => {
    return new Promise(res => setTimeout(() => res(next(url, opts)), delay))
}

/* Returns the url and method without performing an actual request */
const shortCircuit = (next = fetch) => (url, opts) => {
    const response = new Response()
    response.text = () => Promise.resolve(opts.method + "@" + url)
    response.json = () => Promise.resolve({ url, method: opts.method })
    return Promise.resolve(response)
}

/* Logs all requests */
const logFetch = (next = fetch) => (url, opts) => {
    console.log(opts.method + "@" + url)
    return next(url, opts)
}

/* A throttling cache */
const cache = new Map()
let doThrottle = false
const cacheFetch = (throttle = 0, next = fetch) => (url, opts) => {
    const key = opts.method + "@" + url

    if(cache.has(key) && !opts.noCache && doThrottle) {
        return Promise.resolve(cache.get(key))
    }

    if(throttle && !doThrottle) {
        doThrottle = true
        setTimeout(() => { doThrottle = false }, throttle)
    }

    const monkeyPatch = (response, method) => {
        const methodRef = response[method]
        response[method] = () =>
            methodRef.call(response).then(data => {
                cache.get(key)[method] = () => Promise.resolve(data)
                return data
            })
    }
    return next(url, opts).then(_ => {
        cache.set(key, _.clone());
        [ "text", "json", "formData", "blob" ].forEach(method => monkeyPatch(_, method))
        return _
    })
}

// Just use the "middleware" as a polyfill
wretch().polyfills({
    fetch: cacheFetch(1000)
})

// You can even chain them
wretch().polyfills({
   fetch: logFetch(delayFetch(1000, shortCircuit()))
})

I'm thinking about adding this code sample to the doc. to explain the logic !

elbywan commented 6 years ago

Ah also I forgot to mention that fetch already has a built in cache control (if needed) : https://hacks.mozilla.org/2016/03/referrer-and-cache-control-apis-for-fetch/

elbywan commented 6 years ago

@chimon2000 Finally after thinking about it all day I ended up adding a little helper function which should ease things up !

I won't add the middlewares themselves to the wretch codebase to preserve the small size, but feel free to post them in separate repos if you want (like the throttling cache example).

chimon2000 commented 6 years ago

Awesome! I like the idea of keeping the library small and having them as middleware.