使用 Promise.any 选择最快的镜像

之前写的 cmyr-template-cli/草梅项目模板创建器 是直接从 GitHub 下载模板的,但由于众所周知的原因,从 GitHub 上下载模板总是断断续续的,最近就失败了好几次,忍无可忍了所以决定加个镜像源。

一番寻找之下确定了镜像源如下(包含原版 GitHub)

const REMOTES = [
    'https://github.com',
    'https://hub.fastgit.org',
    'https://gitclone.com',
    'https://github.com.cnpmjs.org',
]

然后是使用 Promise.any 函数来选择哪个镜像源是最快的。

/**
 * 从 github 和镜像中选择最快的源
 * @param repository 源 owner/name, 例如 CaoMeiYouRen/rollup-template
 */
export async function getFastGitRepo(repository: string) {
    const loading = ora(`正在选择镜像源 - ${repository}`)
    loading.start()
    try {
        const fast = await Promise.any(REMOTES.map((remote) => {
            const url = `${remote}/${repository}/archive/refs/heads/master.zip`
            return axios({ // 直接利用 axios 发个 HEAD 请求来测试速度, HEAD 请求只会返回响应头,不会返回 body ,所以速度要快一些
                url,
                method: 'HEAD',
                timeout: 15 * 1000,
            })
        }))
        return `direct:${fast.config.url}`
    } catch (error) {
        console.error(error)
        process.exit(1)
    } finally {
        loading.stop()
    }
}

Promise.any 函数是有一个子实例成功就算成功,全部子实例失败才算失败,所以只要第一个镜像源访问成功了就会直接结束,也就不用浪费时间等最后一个返回了。

不过在实际运行的时候发生了 Promise.any not a function 的问题,检查了下发现是我的 Node.js 版本不支持这个函数,所以找了个 shim。

import any from 'promise.any'

if (!Promise.any) {
    Promise.any = any
}

成功执行!