基于JS实现Ajax并发请求的控制 | 青训营

112 阅读4分钟

Ajax、Axios、Fetch的核心区别

Ajax:前后端数据通信(同源、跨域)。 正常的Ajax操作:

let xhr = new XMLHttpRequest();
xhr.open("get", "http://127.0.0.1:8888");
xhr.onreadystatechange = function () {
 if (xhr.readyState === 4 && xhr.status === 200) {
   let text = xhr.responseText;
   console.log(JSON.parse(text));
 }
};
xhr.send();

但是每次我们使用的时候都通过这四步,很麻烦,所以需要对这四步操作进行封装。

axios是对Ajax的封装,是基于Promise管理请求

  • 解决了回调地狱的问题,结合await使用
(async function () {
    let result = await axios.post('/user/login', {
        account: 'XiaoMing',
        password: md5('1234567890')
    });
    
    result = await axios.get('/user/list');
    console.log(result)
})();

Fetch是ES6新增的通信方法,不是ajax,但是它本身实现数据通信,就是基于Promise管理的。

  • 在使用过程中你会发现,我们用fetch不用引入任何东西,直接使用即可,但是每次拿到响应都需要response.json(),所以还需要进一步封装。
  • fetch认为状态码不只是200为成功,400、500...只要服务器有返回都算成功。
(async function () {
    let result = await fetch('http://127.0.0.1:8888/user/login', {
        method: 'post,
        headers: {
            'Content-Type': 'application/x-www-form-urlencoded'
        }
        body: Qs.stringify({
            account: 'XiaoMing',
            password: md5('1234567890')
        })
    }).then(response => {
        return response.json();
    })
    console.log(result)
    
    result = await fetch('http://127.0.0.1:8888/user/list').then(response => {
        return response.join();
    });
    console.log(result)
})();

总结:Ajax/Axios是一个玩意,JQ中的Ajax和axios都是基于XMLHttpRequest对Ajax进行封装,一个是基于promise一个是基于回调函数,fetch和它们都不一样它天生就是一个自带类,通过它就能发请求,基于promise管理,是浏览器提供的一种通信方案。

基于Promise.all实现Ajax的串行和并行

串行: 请求是异步的,需要等待上一个请求成功,才能执行下一个请求

这样的需求在项目中很常见,比如登录逻辑。

并行: 同时发送多个请求(HTTP请求可以同时进行,但是JS的操作都是一步步来的,因为JS是单线程),等待所有请求都成功,我们再去做什么事情...

Promise.all([
    axios.get('/user/list'),
    axios.get('/user/list'),
    axios.get('/user/list'),
    axios.get('/user/list')
]).then(results => {
    console.log(results)
}).catch(reason => {

})

如何控制多个并发请求?

并发限制指的是,每个时刻并发执行的promises数量是固定的,最终的执行结果还是保持与原来的一致

const delay = function delay(interval) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve(interval);
    }, interval);
  });
};

let tasks = [
  () => {
    return delay(1000);
  },
  () => {
    return delay(1003);
  },
  () => {
    return delay(1005);
  },
  () => {
    return delay(1002);
  },
  () => {
    return delay(1004);
  },
  () => {
    return delay(1006);
  },
];

Promise.all(tasks.map((task) => task())).then((results) => {
  console.log(results);
});
// [ 1000, 1003, 1005, 1002, 1004, 1006 ]

但是这样实现是没有并发限制的。

解决方案一:

思路:
  • 并发几个就创建几个工作区,只要有任何一个工作区失败,整体就失败。
  • 工作区分别执行请求,成功一个紧接着添加下一个请求到工作区,一直到所有任务都处理完毕。 tasks数组,数组包含很多方法,每一个方法执行就是发送一个请求(基于Promise管理)
const delay = function delay(interval) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve(interval);
    }, interval);
  });
};

let tasks = [
  () => {
    return delay(1000);
  },
  () => {
    return delay(1003);
  },
  () => {
    return delay(1005);
  },
  () => {
    return delay(1002);
  },
  () => {
    return delay(1004);
  },
  () => {
    return delay(1006);
  },
  () => {
    return delay(1007);
  },
];
function createRequest(tasks, pool) {
  pool = pool || 5;
  let results = [],
    together = new Array(pool).fill(null), // 创建工作区,长度为并发数
    index = 0; // 标记任务顺序
  together = together.map(() => {
    // 每个工作区也用promise管理
    return new Promise((resolve, reject) => {
      // 工作区任务:去整个任务集合中拿任务执行,递归实现
      const run = function () {
        if (index >= tasks.length) {
          resolve();
          return;
        }
        let oldIndex = index, // 保证存储顺序
          task = tasks[index++];
        task()
          .then((result) => {
            results[oldIndex] = result;
            run();
          })
          .catch((reason) => {
            reject(reason);
          });
      };
      run();
    });
  });
  return Promise.all(together).then(() => results);
}
createRequest(tasks, 2)
  .then((results) => {
    // 都成功,整体才是成功,按顺序存储结果
    console.log(results, "-->成功");
  })
  .catch((reason) => {
    // 只要有一个失败,整体就是失败
    console.log(reason, "-->失败");
  });

结果:

ajaxLimit.png 顺序和tasks中顺序一致。

解决方案二:

思路:
  • 第一种方法是根据并发数创建相同数量的工作区,下面的方法是多工作区,全部拿到,但是不全部执行,先存起来
  • 限制多少个就执行多少个,其他不执行,执行完一个再去执行其他,补量执行
function createRequest(tasks, pool, callback) {
  if (typeof pool === "function") {
    callback = pool;
    pool = 5;
  }
  if (typeof pool !== "number") pool = 5;
  if (typeof callback !== "function") callback = function () {};

  class TaskQueue {
    // 正在运行的个数
    running = 0;
    queue = [];
    results = [];
    pushTask(task) {
      let self = this;
      self.queue.push(task);
      self.next();
    }
    // 发请求
    next() {
      let self = this;
      while (self.running < pool && self.queue.length) {
        self.running++;
        let task = self.queue.shift(); // 拿任务,没管控顺序
        task()
          .then((result) => {
            self.results.push(result);
          })
          .finally(() => {
            self.running--;
            self.next();
          });
      }
      if (self.running === 0) callback(self.results);
    }
  }
  let TQ = new TaskQueue();
  tasks.forEach((task) => TQ.pushTask(task));
}

createRequest(tasks, 2, (results) => {
  console.log(results);
});

结果:

微信截图_20230810124550.png 可以看出顺序不一致。