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 });
}
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.
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 ) .