如何使用JS在备用链接中获取可用下载地址

我们官网的app下载地址需要从一个固定地址改为一个备用地址数组。在这个地址数组中会包含多个备选下载地址。我们需要实现一个机制,能够从这个备用地址数组中动态获取一个实际可用的下载地址。这个机制需要满足以下要求:

1. 输入:一个包含多个备选下载地址的数组

2. 处理:循环检测数组中的每个下载地址,判断其是否是一个可访问的有效地址

3. 输出:从数组中获取到的第一个可访问的地址,或者如果全部不可访问 then 报错

4. 需要考虑异步检测多个地址的情况,并在得到一个可用地址后立即取消检测余下地址的请求

5. 需要适当的异常处理机制来捕获整个过程中可能出现的各种异常情况

6. 这个机制会定期运行,以实时监测备用地址数组中的地址变更,并动态更新官网显示的app下载地址到一个可访问的有效地址。这可以最大限度减少用户下载失败的概率,并提高用户体验。

我开始的思路是直接循环去请求下载地址,获取可用的:

js
// 备用下载地址数组
let downloadUrls = [
  'https://download1.example.com/app.apk',
  'https://download2.example.com/app.apk',
  'https://download3.example.com/app.apk'
]

// 获取可用下载地址函数
function getAvailableUrl() {
  let availableUrl;
  
  // 循环遍历地址数组
  for (let url of downloadUrls) {
    // 使用axios HEAD请求检测地址是否可访问
    axios.head(url)
      .then(response => {
        if (response.status >= 200 && response.status < 400) {
          // 如果可访问,则设置此地址并退出循环
          availableUrl = url;
          break; 
        }
      })
  }
  
  // 返回获取到的可用下载地址
  return availableUrl; 
}

// 调用函数获取下载地址并输出
let downloadUrl = getAvailableUrl();
console.log(downloadUrl);

后来发现有异步问题,由于JavaScript的单线程特性,我们发起的HEAD请求会异步执行。这意味着当代码执行到请求语句时,它不会 block 后续代码的执行,而会将请求放入请求队列中异步执行。

所以,当第一个请求的响应还没返回时,循环会继续迭代,发起第二个请求,以此类推。这会导致所有的请求都会被发出去,这不是我们想要的效果。

尝试用promis改写下这个版本:

js
/**
 * 获取可用下载地址
 * @param {Array} downloadUrls 备用下载地址数组
 * @returns {Promise} 包含可用下载地址的Promise
 */
function getAvailableUrl(downloadUrls) {
  return new Promise((resolve, reject) => {
    // 循环遍历地址数组
    for (let url of downloadUrls) {  
      // 使用axios HEAD请求检测地址是否可访问  
      axios.head(url)
        .then(response => {
          // 如果可访问,通过Promise resolve返回此地址
          if (response.status >= 200 && response.status < 400) {
            resolve(url);
          }
        })
        .catch(err => reject(err))  // 捕获异常,通过reject返回
    }
  })
}

// 传入备用下载地址数组
let downloadUrls = [
  'https://download1.example.com/app.apk',
  'https://download2.example.com/app.apk',
  'https://download3.example.com/app.apk'
]  

// 调用函数获取下载地址  
getAvailableUrl(downloadUrls) 
  .then(availableUrl => console.log(availableUrl))
  .catch(err => console.log(err))

这个代码实现的主要逻辑是:

1. 定义一个带参数downloadUrls的getAvailableUrl函数,参数为数组,返回Promise

2. 在函数内使用Promise处理异步循环遍历地址检测

3. 如果可用地址响应成功,通过resolve返回该地址

4. 任何错误使用reject捕获并返回

5. 调用函数传入备用地址数组,并处理then及catch

6. 在then中处理resolve情况并输出可用地址,在catch中输出异常

这是一个完整的示例,说明了如何使用Promise和axios来处理异步请求以及错误处理,最终获取一个可用结果。

到这儿功能是完成了,但还有一个问题就是,这个连接的请求走完要很久,我需要找到可用的连接后,马上取消所有请求,于是就有了最终一版:

js
/**
 * 获取可用下载地址
 * @param {Array} downloadUrls 备用下载地址数组 
 * @returns {Promise} 包含可用下载地址的Promise
 */
function getAvailableUrl(downloadUrls) {
  let cancel;  // 定义cancel变量用于保存取消函数 

  return new Promise((resolve, reject) => {
    // 循环遍历地址数组
    for (let url of downloadUrls) {
      // 发起HEAD请求检测地址是否可访问  
      const request = axios.head(url);
      cancel = request.cancel;  // 保存取消函数以便之后使用

      request
        .then(response => { 
          // 如果可访问,取消余下请求,resolve此地址并退出函数
          if (response.status >= 200 && response.status < 400) {
            cancel();
            resolve(url);
            break;
          }
        })
        .catch(err => reject(err))  
    }
  }) 
}

改进之处主要有:

1. 定义一个cancel变量保存 axios 请求的 cancel 函数

2. 在发起每个axios请求时,通过 request.cancel 获取取消函数并保存到cancel变量

3. 一旦得到一个可访问地址的响应,调用cancel()函数取消余下请求

4. 取消请求后通过resolve返回该可用地址,并通过break退出循环

5. catch仍然通过reject返回任何错误通过保存每个请求的取消函数,我们现在有能力在需要时取消请求,避免额外资源消耗。这可以提高代码的性能与效率。

over~

posted @ 2023-04-15 17:13  思绪在漫游  阅读(556)  评论(0)    收藏  举报