examples/load-balancer/lib/balancer.js
function balancer(backends) {
    const tracked = backends.map((h) => {
        if (typeof h !== "function") {
            throw Error("Backend must be a fetch like function");
        }
        return {
            proxy: h,
            requestCount: 0,
            scoredRequestCount: 0,
            statuses: Array(10),
            lastError: 0,
            healthScore: 1,
            errorCount: 0
        };
    });
    const fn = async function fetchBalancer(req, init) {
        if (typeof req === "string") {
            req = new Request(req);
        }
        const attempted = new Set();
        while (attempted.size < tracked.length) {
            let backend = null;
            const [backendA, backendB] = chooseBackends(tracked, attempted);
            if (!backendA) {
                return new Response("No backend available", { status: 502 });
            }
            if (!backendB) {
                backend = backendA;
            }
            else {
                // randomize between 2 good candidates
                backend = (Math.floor(Math.random() * 2) == 0) ? backendA : backendB;
            }
            const promise = backend.proxy(req, init);
            if (backend.scoredRequestCount != backend.requestCount) {
                // fixup score
                // this should be relatively concurrent with the fetch promise
                score(backend);
            }
            backend.requestCount += 1;
            attempted.add(backend);
            let resp;
            try {
                resp = await promise;
            }
            catch (e) {
                resp = proxyError;
            }
            if (backend.statuses.length < 10) {
                backend.statuses.push(resp.status);
            }
            else {
                backend.statuses[(backend.requestCount - 1) % backend.statuses.length] = resp.status;
            }
            if (resp.status >= 500 && resp.status < 600) {
                backend.lastError = Date.now();
                // always recompute score on errors
                score(backend);
                // clear out response to trigger retry
                if (canRetry(req, resp)) {
                    continue;
                }
            }
            return resp;
        }
        return proxyError;
    };
    return Object.assign(fn, { backends: tracked });
}
function balancer(backends) {
    const tracked = backends.map((h) => {
        if (typeof h !== "function") {
            throw Error("Backend must be a fetch like function");
        }
        return {
            proxy: h,
            requestCount: 0,
            scoredRequestCount: 0,
            statuses: Array(10),
            lastError: 0,
            healthScore: 1,
            errorCount: 0
        };
    });
    const fn = async function fetchBalancer(req, init) {
        if (typeof req === "string") {
            req = new Request(req);
        }
        const attempted = new Set();
        while (attempted.size < tracked.length) {
            let backend = null;
            const [backendA, backendB] = chooseBackends(tracked, attempted);
            if (!backendA) {
                return new Response("No backend available", { status: 502 });
            }
            if (!backendB) {
                backend = backendA;
            }
            else {
                // randomize between 2 good candidates
                backend = (Math.floor(Math.random() * 2) == 0) ? backendA : backendB;
            }
            const promise = backend.proxy(req, init);
            if (backend.scoredRequestCount != backend.requestCount) {
                // fixup score
                // this should be relatively concurrent with the fetch promise
                score(backend);
            }
            backend.requestCount += 1;
            attempted.add(backend);
            let resp;
            try {
                resp = await promise;
            }
            catch (e) {
                resp = proxyError;
            }
            if (backend.statuses.length < 10) {
                backend.statuses.push(resp.status);
            }
            else {
                backend.statuses[(backend.requestCount - 1) % backend.statuses.length] = resp.status;
            }
            if (resp.status >= 500 && resp.status < 600) {
                backend.lastError = Date.now();
                // always recompute score on errors
                score(backend);
                // clear out response to trigger retry
                if (canRetry(req, resp)) {
                    continue;
                }
            }
            return resp;
        }
        return proxyError;
    };
    return Object.assign(fn, { backends: tracked });
}
A fetch function load balancer. Distributes requests to a set of backends; attempts to
send requests to most recently healthy backends using a 2 random (pick two healthiest,
randomize which gets requests).

If all backends are healthy, tries to evenly distribute requests as much as possible.

When backends return server errors (500-599) it retries idempotent requests
until it gets a good response, or all backends have been tried.

@param backends fetch functions for each backend to balance accross
@returns a function that behaves just like fetch, with a `.backends` property for
retrieving backend stats.