<!DOCTYPE html>
<html>

<head>
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  <meta charset="utf-8" />
  <meta name="referrer" content="never">
  <title>JavaScript 请求服务负载均衡,仿 nginx upstream</title>
</head>

<body>
  <h4>请打开控制台或按F12</h4>
  <button id="btn1">可用的源</button>
  <button id="btn2">最快的源</button>
  <button id="btn3">自定义超时</button>
  <button id="btn4">获取天气</button>
  <div id="result"></div>
</body>

</html>
/*
    upstream: From nginx upstream
    Source must support cross-domain
    v1.0.1
    by netnr
    2021-01-15
 */

(function (window) {
    var ups = function (hosts, callback, timeout) {
        //全局对象、默认请求超时、默认源过期
        var gk = "upstreamCache", dto = 3000, es = 30000;
        if (!(gk in window)) {
            try {
                window[gk] = JSON.parse(localStorage.getItem(gk)) || {};
            } catch (e) {
                window[gk] = {}
            }
        }

        var startTime = new Date().valueOf(),
            cacheKey = hosts.join(','),
            hostsCache = window[gk][cacheKey];

        if (hostsCache && startTime - hostsCache.date < es) {
            callback(hostsCache.ok[0], hostsCache.ok, true);
        } else {
            var ok = [], bad = 0, i = 0, len = hosts.length;
            for (; i < len;) {
                var host = hosts[i++];
                //自动补齐链接
                host = host.trim().toLowerCase().indexOf("//") >= 0 ? host : "//" + host;
                //发起fetch,添加成功的url(该url与hosts可能不一样),须支持跨域请求
                fetch(host).then(function (res) {
                    res.ok ? ok.push(res.url) : bad++;
                }).catch(() => bad++)
            }
            var si = setInterval(function () {
                var isc = false, now = new Date().valueOf();
                //当timeout为1,返回最快可用的host
                if (timeout == 1 && ok.length > 0) {
                    isc = true;
                }
                //所有请求结束 或 超时,返回结果
                var istimeout = now - startTime > ((timeout == 1 || !timeout) ? dto : timeout);
                if (ok.length + bad == len || istimeout) {
                    isc = true;
                }
                if (isc) {
                    clearInterval(si);
                    window[gk][cacheKey] = { date: now, ok: ok };
                    localStorage.setItem(gk, JSON.stringify(window[gk]));
                    callback(ok[0], ok, false);
                }
            }, 1)
        }
    }

    window.upstream = ups;

    return ups;
})(window);



//示例
var hosts = ["cors.eu.org", "bird.ioliu.cn/v2?url=", "api.github.com"];

document.getElementById("btn1").onclick = function () {
    upstream(hosts, function (fast, ok) {
        console.log(fast, ok)
        log(fast, ok)
    });
}

document.getElementById("btn2").onclick = function () {
    upstream(hosts, function (fast, ok) {
        console.log(fast, ok)
        log(fast, ok)
    }, 1);
}

document.getElementById("btn3").onclick = function () {
    upstream(hosts, function (fast, ok) {
        console.log(fast, ok)
        log(fast, ok)
    }, 280);
}

document.getElementById("btn4").onclick = function () {
    upstream(["https://cors.eu.org/", "https://bird.ioliu.cn/v2?url="], function (fast) {
        var api = "http://wthrcdn.etouch.cn/weather_mini?citykey=101040100";
        var url = fast + api;
        fetch(url).then(res => res.json()).then(function (data) {
            console.log(data);
            log(data);
        });
    }, 1);
}

function log() {
    var ret = document.getElementById("result");
    for (var i = 0; i < arguments.length; i++) {
        var msg = arguments[i];
        if (typeof msg != "string") {
            msg = JSON.stringify(msg);
        }
        var pre = document.createElement("pre");
        pre.innerHTML = msg;
        ret.appendChild(pre);
    }
    ret.appendChild(document.createElement("hr"))
    ret.scrollTo(0, 9999999);
}
#result{
    color:#fff;
    overflow: auto;
    margin-top: 30px;
    max-height: 300px;
    background-color: #333;
    padding: 0 15px;
}